基本TCP响应服务器一次只能处理一个客户端的请求。当一个客户端向一个已经被其他客户端占用的服务器发送连接请求时,虽然其在连接建立后即可向服务器端发送数据,服务器端在处理完已有客户端的请求前,却不会对新的客户端作出响应,。这种类型的服务器称为"迭代服务器(iterative server)"。迭代服务器按顺序处理客户端的请求,也就是说在完成了对前一客户端的服务后,才会对下一个客户端进行响应。这种服务器最适用于每个客户端所请求的连接时间都被限制在较小范围内的应用中,而对于允许客户端请求长时间服务的情况,后续客户端将面临无法接受的长时间等待。
客户端代码,用于测试:
客户端线程类:
package com.iteye.xuj.socket; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.net.SocketException; import java.net.UnknownHostException; import java.util.logging.Logger; public class SpendProtocol implements Runnable { private Socket socket = null; private byte[] data = null; private Logger logger; public SpendProtocol(Socket socket, byte[] data, Logger logger) { this.socket = socket; this.data = data; this.logger = logger; } public static void spend(Socket socket, byte[] data, Logger logger) throws UnknownHostException, IOException { logger.info("Connected to server...sending echo string"); // 获取套接字的输入输出流 InputStream in = socket.getInputStream(); OutputStream out = socket.getOutputStream(); // 发送字符串到回馈服务器 out.write(data); // 从回馈服务器接受回馈信息 int totalBytesRcvd = 0; int bytesRcvd; while (totalBytesRcvd < data.length) { if ((bytesRcvd = in.read(data, totalBytesRcvd, data.length - totalBytesRcvd)) == -1) throw new SocketException("Connection closed prematurely"); totalBytesRcvd += bytesRcvd; } logger.info("Received: " + new String(data)); // 关闭套接字 socket.close(); } @Override public void run() { try { spend(socket, data, logger); } catch (Exception e) { e.printStackTrace(); } } }
客户端类,发送指定条数的信息:
package com.iteye.xuj.socket; import java.net.Socket; import java.util.logging.Logger; import java.io.IOException; public class TestClient { public static void main(String[] args) throws IOException { // 地址、数据、端口 String server = "127.0.0.1"; int servPort = 7; final Logger logger = Logger.getLogger("practical"); // 发送10个请求 int sendSize = 10; for (int i = 0; i < sendSize; i++) { byte[] data = ("测试数据" + i).getBytes(); Socket socket = new Socket(server, servPort); // 创建一个新的Thread实例来处理新的连接 Thread thread = new Thread(new SpendProtocol(socket, data, logger)); // 为连接开始执行新的线程 thread.start(); } } }
服务器端线程类:
package com.iteye.xuj.socket; 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; //声明实现Runnable接口 public class EchoProtocol implements Runnable { // 类成员变量 private static final int BUFSIZE = 32; private Socket clntSock; private Logger logger; // 构造函数 public EchoProtocol(Socket clntSock, Logger logger) { this.clntSock = clntSock; this.logger = logger; } // 实现回显协议 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]; // 接收和回显 while ((recvMsgSize = in.read(echoBuffer)) != -1) { out.write(echoBuffer, 0, recvMsgSize); totalBytesEchoed += recvMsgSize; } // 在日志中记录连续的详细信息,同时记录远端的SocketAddress和回显的字节数 logger.info("Client " + clntSock.getRemoteSocketAddress() + ", echoed " + totalBytesEchoed + " bytes."); } catch (IOException ex) { // 将异常写入日志 logger.log(Level.WARNING, "Exception in echo protocol", ex); } finally { try { clntSock.close(); } catch (IOException e) { } } } public void run() { handleEchoClient(clntSock, logger); } }
一客户一线程服务器:
在一客户一线程( thread-per-client ) 的服务器中, 为每个连接都创建了一个新的线程来处理。 服务器循环执行一些任务, 在指定端口上侦听连接, 反复接收客户端传入的连接请求,并为每个连接创建一个新的线程来对其进行处理。
服务器代码:
package com.iteye.xuj.socket; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.logging.Logger; public class TCPEchoServerThread { public static void main(String[] args) throws IOException { int echoServPort = 7; ServerSocket servSock = new ServerSocket(echoServPort); Logger logger = Logger.getLogger("practical"); // 一直反复循环,处理传入的连接请求 while (true) { // 接收传入的连接请求 Socket clntSock = servSock.accept(); // 创建一个新的Thread实例来处理新的连接 Thread thread = new Thread(new EchoProtocol(clntSock, logger)); // 为连接开始执行新的线程 thread.start(); // 记录日志 logger.info("Created and started Thread " + thread.getName()); } } }
启动服务器,再运行客户端:
服务器端输出:
2013-6-27 10:22:17 com.iteye.xuj.socket.TCPEchoServerThread main 信息: Created and started Thread Thread-1 2013-6-27 10:22:17 com.iteye.xuj.socket.TCPEchoServerThread main 信息: Created and started Thread Thread-2 2013-6-27 10:22:17 com.iteye.xuj.socket.TCPEchoServerThread main 信息: Created and started Thread Thread-3 2013-6-27 10:22:17 com.iteye.xuj.socket.TCPEchoServerThread main 信息: Created and started Thread Thread-4 2013-6-27 10:22:17 com.iteye.xuj.socket.TCPEchoServerThread main 信息: Created and started Thread Thread-5 2013-6-27 10:22:17 com.iteye.xuj.socket.TCPEchoServerThread main 信息: Created and started Thread Thread-6 2013-6-27 10:22:17 com.iteye.xuj.socket.TCPEchoServerThread main 信息: Created and started Thread Thread-7 2013-6-27 10:22:17 com.iteye.xuj.socket.TCPEchoServerThread main 信息: Created and started Thread Thread-8 2013-6-27 10:22:17 com.iteye.xuj.socket.TCPEchoServerThread main 信息: Created and started Thread Thread-9 2013-6-27 10:22:17 com.iteye.xuj.socket.TCPEchoServerThread main 信息: Created and started Thread Thread-10 2013-6-27 10:22:17 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29702, echoed 13 bytes. 2013-6-27 10:22:17 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29701, echoed 13 bytes. 2013-6-27 10:22:17 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29704, echoed 13 bytes. 2013-6-27 10:22:17 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29710, echoed 13 bytes. 2013-6-27 10:22:17 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29708, echoed 13 bytes. 2013-6-27 10:22:17 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29709, echoed 13 bytes. 2013-6-27 10:22:17 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29706, echoed 13 bytes. 2013-6-27 10:22:17 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29703, echoed 13 bytes. 2013-6-27 10:22:17 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29707, echoed 13 bytes. 2013-6-27 10:22:17 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29705, echoed 13 bytes.
客户端输出:
2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据9 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据1 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据5 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据3 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据8 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据0 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据2 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据4 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据6 2013-6-27 10:22:17 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据7
线程池
每个新线程都会消耗系统资源:创建一个线程将占用 CPU 周期,而且每个线程都自己的数据结构(如,栈)也要消耗系统内存。另外,当一个线程阻塞( block )时, JVM 将保存其状态, 选择另外一个线程运行, 并在上下文转换( context switch ) 时恢复阻塞线程的状态。 随着线程数的增加, 线程将消耗越来越多的系统资源。 这将最终导致系统花费更多的时间来处理上下文转换和线程管理, 更少的时间来对连接进行服务。 那种情况下, 加入一个额外的线程实际上可能增加客户端总服务时间。
可以通过限制总线程数并重复使用线程来避免这个问题。与为每个连接创建一个新的线程不同,服务器在启动时创建一个由固定数量线程组成的线程池( thread pool )。当一个新的客户端连接请求传入服务器, 它将交给线程池中的一个线程处理。 当该线程处理完这个客户端后,又返回线程池,并为下一次请求处理做好准备。如果连接请求到达服务器时,线程池中的所有线程都已经被占用,它们则在一个队列中等待,直到有空闲的线程可用。
服务器代码:
package com.iteye.xuj.socket; import java.io.IOException; import java.net.ServerSocket; import java.net.Socket; import java.util.logging.Level; import java.util.logging.Logger; public class TCPEchoServerPool { public static void main(String[] args) throws IOException { int echoServPort = 7; int threadPoolSize = 5; final ServerSocket servSock = new ServerSocket(echoServPort); final Logger logger = Logger.getLogger("practical"); // 创建并启动threadPoolSize个新线程 for (int i = 0; i < threadPoolSize; i++) { Thread thread = new Thread() { public void run() { while (true) { try { // 接受连接请求 Socket clntSock = servSock.accept(); // 将客户端套接字传递给EchoProtocol.handleEchoClient()方法 EchoProtocol.handleEchoClient(clntSock, logger); } catch (IOException ex) { logger.log(Level.WARNING, "Client accept failed", ex); } } } }; thread.start(); logger.info("Created and started Thread = " + thread.getName()); } } }
启动服务顺,再运行客户端:
服务器启动时输出:
2013-6-27 10:28:09 com.iteye.xuj.socket.TCPEchoServerPool main 信息: Created and started Thread = Thread-1 2013-6-27 10:28:09 com.iteye.xuj.socket.TCPEchoServerPool main 信息: Created and started Thread = Thread-2 2013-6-27 10:28:09 com.iteye.xuj.socket.TCPEchoServerPool main 信息: Created and started Thread = Thread-3 2013-6-27 10:28:09 com.iteye.xuj.socket.TCPEchoServerPool main 信息: Created and started Thread = Thread-4 2013-6-27 10:28:09 com.iteye.xuj.socket.TCPEchoServerPool main 信息: Created and started Thread = Thread-5
运行客户端后服务器端输出:
2013-6-27 10:28:18 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29732, echoed 13 bytes. 2013-6-27 10:28:18 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29730, echoed 13 bytes. 2013-6-27 10:28:18 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29729, echoed 13 bytes. 2013-6-27 10:28:18 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29728, echoed 13 bytes. 2013-6-27 10:28:18 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29731, echoed 13 bytes. 2013-6-27 10:28:18 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29734, echoed 13 bytes. 2013-6-27 10:28:18 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29736, echoed 13 bytes. 2013-6-27 10:28:18 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29735, echoed 13 bytes. 2013-6-27 10:28:18 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29737, echoed 13 bytes. 2013-6-27 10:28:18 com.iteye.xuj.socket.EchoProtocol handleEchoClient 信息: Client /127.0.0.1:29733, echoed 13 bytes.
客户端输出:
2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Connected to server...sending echo string 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据4 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据2 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据1 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据0 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据3 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据6 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据8 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据7 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据9 2013-6-27 10:28:18 com.iteye.xuj.socket.SpendProtocol spend 信息: Received: 测试数据5