Java Socket长连接首次回显后读写速度下降问题排查
Socket长连接耗时异常高于短连接问题分析
原本预期Socket长连接在多次消息交互时性能更优,但实际测试中,长连接首次消息后读写平均耗时达80ms,而每次新建并关闭Socket的短连接耗时始终为0ms,以下是测试代码及现象:
长连接代码示例
public class StackOverflow { public static void main(String[] args) throws IOException, InterruptedException { new Thread() { public void run() { try { ServerSocket server = new ServerSocket(8080); Socket socket = server.accept(); while (true) { byte data[] = read(socket); write(socket, data); } } catch (IOException e) { e.printStackTrace(); } } }.start(); InetSocketAddress sockaddr = new InetSocketAddress("localhost", 8080); Socket socket = new Socket(); socket.connect(sockaddr, 60000); byte data[] = new byte[100]; new Random().nextBytes(data); while (true) { long start = System.currentTimeMillis(); write(socket, data); read(socket); long end = System.currentTimeMillis(); System.out.printf("Time: %d\n", end - start); } } public static void write(Socket socket, byte data[]) throws IOException { OutputStream out = socket.getOutputStream(); ByteBuffer buffer = ByteBuffer.allocate(Integer.BYTES); buffer.putInt(data.length); out.write(buffer.array()); out.write(data); out.flush(); } public static byte[] read(Socket socket) throws IOException { InputStream in = socket.getInputStream(); byte head[] = new byte[Integer.BYTES]; in.read(head); ByteBuffer buffer = ByteBuffer.wrap(head); int dataSize = buffer.getInt(); byte data[] = new byte[dataSize]; in.read(data); return data; } }
输出结果:
Time: 0 Time: 96 Time: 88 Time: 87 Time: 88 Time: 88 Time: 91 Time: 92 Time: 92 Time: 87 Time: 91 ...
短连接代码示例
public static void main(String[] args) throws IOException, InterruptedException { new Thread() { public void run() { try { ServerSocket server = new ServerSocket(8080); while (true) { Socket socket = server.accept(); byte data[] = read(socket); write(socket, data); socket.close(); } } catch (IOException e) { e.printStackTrace(); } } }.start(); InetSocketAddress sockaddr = new InetSocketAddress("localhost", 8080); byte data[] = new byte[100]; new Random().nextBytes(data); while (true) { Socket socket = new Socket(); socket.connect(sockaddr, 60000); long start = System.currentTimeMillis(); write(socket, data); read(socket); long end = System.currentTimeMillis(); System.out.printf("Time: %d\n", end - start); socket.close(); } }
输出结果:
Time: 0 Time: 0 Time: 0 Time: 0 Time: 0 Time: 0 Time: 0 Time: 0 Time: 0 Time: 0 Time: 0 Time: 0 Time: 0 Time: 0 Time: 0 Time: 0 Time: 0 ...
原因分析
核心原因是TCP的Nagle算法与延迟确认(Delayed ACK)机制的交互冲突:
- 长连接场景:客户端首次发送数据后,服务器回写响应,客户端读取响应后立刻发送下一条数据。由于这条数据体积较小(仅104字节),TCP的Nagle算法会等待:要么积累更多数据一起发送,要么等待服务器对前一次数据的ACK。而服务器收到客户端的第二条数据后,会触发延迟确认机制(默认延迟约80-200ms),不会立即回复ACK。Nagle算法等待ACK超时后才发送数据,最终导致每次交互出现80ms左右的延迟。
- 短连接场景:每次交互完成后客户端主动关闭Socket,TCP在关闭连接时会发送FIN包,服务器收到FIN后会立即回复ACK,同时回写的数据会被强制发送,不会触发延迟确认;此外,短连接的TCP握手会触发快速启动机制,加上关闭连接时的缓冲区强制刷新,完全规避了Nagle算法的等待逻辑,因此耗时始终为0ms。
另外需要注意:代码中的in.read(head)和in.read(data)没有处理读取不完整的情况(InputStream.read()可能只返回部分字节),这在生产环境中会导致数据解析错误,需要通过循环读取直到获取完整的目标字节数来修复,但这不是本次耗时异常的直接原因。
内容的提问来源于stack exchange,提问作者Rodrigo Vale
相关产品推荐
相关产品推荐

