【Java TCP/IP Socket编程】----进阶----多任务处理

简介

 基本的TCP相应服务器是一次只能处理一个客户端请求,无法处理同时多个客户端请求,Java中多线程技术解决这一问题。多线程有两种方式:一是一客户一线程;二是线程池;

1)一客户一线程:即为每个连接创建一个线程来处理,服务器端会循环执行,监听指定端口的连接,反复接收来自客户端的连接请求,并为每个连接创建一个新线程来对其处理。

    缺点:一客户一线程的方式虽然处理可以多个客户端请求,但每个新线程都会消耗系统资源(如CPU),并且每个线程独有自己的数据结构(如栈)也要消耗系统内存。另外一个线程阻塞是,JVM会保存其状态,选择另外一个线程运行,并在上下文转换时恢复阻塞线程的状态。随着线程数的增加,线程将消耗越来越多的资源。这会导致系统将花费更多时间来处理上下文的转换和线程管理,更少的时间来对连接服务,加入额外的线程实际上可能会增加客户端总服务时间。

    解决:通过限制总线程数并重复使用线程来避免一客户一线程的缺陷。

2)使用线程池:与为每个线程创建新的线程不同,服务器在启动时创建固定数量的线程组成的线程池。当有一个客户端请求过来时,线程池将分配一个线程处理,线程在处理完请求后将会返回线程池,为下一次请求处理做好准备。如果连接请求到达服务器端,线程池中所有的线程都已被占用,它们则在一个队列中等待,直到有空闲的线程可用。

线程池服务端具体实现的步骤:

      1.服务器端创建一个ServerSocket实例。

      2.创建N个线程,每个线程都反复循环,从(共享的)ServerSocket实例中接收客户端连接。当多个线程同时调用同一个ServerSocket实例的accept()方法将会阻塞等待,直到一个新连接创建成功。

      3.新建立连接对应Socket实例则只在选中的线程中返回。其他线程将阻塞,直到成功建立下一个连接和选中下一个幸运的线程。

    缺点:创建的线程池太少,客户端可能等待很长时间才能获取服务,线程池大小不能根据客户端请求数量进行调整。

    解决:Java中提供一个调度工具(系统管理调用接口Executor),可以在系统负载时扩展线程池的大小,负载较轻时缩减线程池的大小。

3)系统管理调度:接口Executor

Executor接口代表了一个根据某种策略来执行Runnable实例的对象,其中包含了排队和调度的细节,或者如何选择要执行的任务。Executor接口只定义了一个方法:

public interface Executor {
  void execute(Runnable command);
}

Java中内置了大量的Executor接口实现,很简单使用,也可以进行扩展性配置。其中一些还提供了处理维护线程等繁琐细节的功能。ExecutorService接口继承于Executor接口,提供了一个更高级的工具来关闭服务器,包括正常关闭和突然关闭。ExecutorService还允许在完成任务后返回一个结果,这需要用到Callable接口,它和Runnable接口很像,只是多了一个返回值。

阻塞和超时

Socket的调用可能存多种原因而阻塞。

1)read(),receive()方法在没有数据可读时会阻塞。ServerSocket的accept()方法和Socket构造方法会阻塞等待,直到建立连接。

解决:使用socket,ServerSocket,以及DatagramSocket类的setSoTimeout()方法,来设置其阻塞的最长时间。指定时间内方法没有返回将会抛出异常InterruptedIOException。对于Socket实例,可以在read()方法前,在套接字的InputStream上调用available()方法检测是否有可读数据。

2)连接和写数据

Socket类的构造函数会尝试根据参数中指定的主机和端口来建立连接,并阻塞等待,直到连接成功建立或发生了系统定义的超时。不幸的是,系统定义的超时时间很长,而Java又没有提供任何缩短它的方法。要改变这种情况,可以使用Socket类的无参数构造函数,它返回的是一个没有建立连接的Socket实例。需要建立连接时,调用该实例的connect()方法,并指定一个远程终端和超时时间。

write()方法调用也会阻塞等待,直到最后一个字节成功写入到了TCP实现的本地缓存中。如果可用的缓存空间比要写入的数据小,在write()方法调用返回前,必须把一些数据成功传输到连接的另一端。

3)限制客户端的时间

通过服务期限和当前计算出截止时间,每次调用read()结束后,重新计算当前和截止时间的差值,即服务截止时间,并将套接字的超时时间设置为该剩余时间。

package com.tcp.ip.chapter4;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.logging.Level;
import java.util.logging.Logger;
 
public class TimeLimitEchoProtocol implements Runnable {
 
	private static final int BUFSIZE = 32;
	private static final String TIMELIMIT = "10000";
	private static final String TIMELIMITPROP = "Timelimit";
	
	private static int timelimit;
	
	private Socket clntSock;
 
	private Logger logger;
	
	public TimeLimitEchoProtocol(Socket clntSock, Logger logger){
		
		this.clntSock = clntSock;
		this.logger = logger;
		timelimit = Integer.parseInt(System.getProperty(TIMELIMITPROP, TIMELIMIT));
	}
	
	public static void handleEchoClient(Socket clntSock, Logger logger){
		try {
			InputStream in = clntSock.getInputStream();
			OutputStream out = clntSock.getOutputStream();
			int recvMsgSize; 
			int totalBytesEchoed = 0;
			byte[] echoBuffer = new byte[BUFSIZE];
			//算出最后结束的时间
			long endTime = System.currentTimeMillis() + timelimit;
			int timeBoundMillis = timelimit;
			clntSock.setSoTimeout(timeBoundMillis);
			
			while ((timeBoundMillis > 0) && 
					((recvMsgSize = in.read(echoBuffer)) != -1)) {
				out.write(echoBuffer, 0, recvMsgSize);
				totalBytesEchoed += recvMsgSize;
				//当前剩余时间
				timeBoundMillis = (int) (endTime - System.currentTimeMillis());
				clntSock.setSoTimeout(timeBoundMillis);
				logger.info("客户端 " + clntSock.getRemoteSocketAddress() +
						", echoed " + totalBytesEchoed + " bytes.");
				
			}
		} catch (IOException ex) {
			logger.log(Level.WARNING, "Exception in echo protocol" , ex);
		}
	}
	
	public void run() {
		handleEchoClient(this.clntSock, this.logger);
		
	}
}

TCP多线程案例

客户端的代码

public class TCPEchoClient {
  public static void main(String[] args) throws IOException {
    Socket clientSocket = new Socket("127.0.0.1",1234);
    //键盘录入
    BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
    //发送到客户端
    PrintStream out = new PrintStream(clientSocket.getOutputStream());
    //读取从服务器端的回复
    BufferedReader  getBr = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
    String input = null;
    while((input = br.readLine())!=null) {
      out.println(input);
      if("end".equals(input)) {
        break;
      }
      String echoInfo = getBr.readLine();
      System.out.println("Information from Server "+clientSocket.getRemoteSocketAddress()+": "+echoInfo);
    }
    clientSocket.close();
  }
}

处理任务的类

public class EchoProtocol implements Runnable{
    
  private Socket clientSocket;
  private Logger logger;
  public EchoProtocol(Socket socket ,Logger logger) {
    this.clientSocket= socket;
    this.logger = logger;
  }
  
  public static void handleEchoClient(Socket clientSocket,Logger logger) {
    try {
      //从客户端读取
      BufferedReader br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
      //从客户端读取的信息,加echo+info进行回复
      PrintStream out = new PrintStream(clientSocket.getOutputStream());
      String info = null;
      while((info =br.readLine())!=null) {
        if("end".equals(info)) {
          break;
        }
        System.out.println("information from client "+clientSocket.getRemoteSocketAddress()+": "+info);
        out.println("echo:"+info);
      }
      clientSocket.close();
    }catch(IOException e) {
    }
  }
  @Override
  public void run() {
    handleEchoClient(clientSocket,logger);
  }
}

1.一客户一线程的方式

public class TCPEchoServerThread {
  public static void main(String[] args) throws IOException {
    ServerSocket serverSocket = new ServerSocket(1234);
    Logger logger = Logger.getLogger("practical");
    while(true) {
      Socket clientSocket = serverSocket.accept();
      Thread thread = new Thread(new EchoProtocol(clientSocket,logger));
      thread.start();
      logger.info("Created and started Thread "+thread.getName());
    }
  }
}

2.线程池的方式

public class TCPEchoServerPool {
  private static final int THREADPOOL = 5;
  public static void main(String[] args) throws IOException {
    ServerSocket serverSocket = new ServerSocket(1234);
    Logger logger = Logger.getLogger("practical");
    for(int i = 0; i < THREADPOOL;i++) {
      Thread thread = new Thread() {
        public void run() {
          while(true) {
            try {
              Socket clientSocket = serverSocket.accept();
              EchoProtocol.handleEchoClient(clientSocket, logger);
            } catch (IOException e) {
              e.printStackTrace();
            }
          }
        }
      };
      thread.start();
      logger.info("Created and Started thread :"+thread.getName());
    }
  }
}

3.系统管理调度:Executor接口

public class TCPEchoServerExecutor {
  public static void main(String[] args) throws IOException {
    ServerSocket socket = new ServerSocket(1234);
    ExecutorService executor = Executors.newCachedThreadPool();
    Logger logger = Logger.getLogger("pratical");
    while(true) {
      Socket clientSocket = socket.accept();
      executor.submit(new EchoProtocol(clientSocket,logger));
    }
  }
}

猜你喜欢

转载自blog.csdn.net/lili13897741554/article/details/83030014