Java网络编程学习A轮_04_TCP连接异常

时间:2023-12-11 23:10:08

参考资料:

https://huoding.com/2016/01/19/488

示例代码:

https://github.com/gordonklg/study,socket module

A. CLOSE_WAIT

有时会出现服务器响应极慢、假死的现象,查看 netstat 会发现服务器上存在大量未关闭的 CLOSE_WAIT 状态连接。我们分析下原因。

首先,CLOSE_WAIT 是被动关闭方才会出现的状态。我们模拟一个场景,客户端建立大量 Socket 连接,同时为每个 Socket 设置超时时间,并且在发生超时时关闭 Socket;服务器端不发送数据也不关闭 Socket。对应测试代码如下:

gordon.study.socket.basic.wireshark.MockSocketTimeoutThenServerCloseWait_Server.java

public class MockSocketTimeoutThenServerCloseWait_Server {

    @SuppressWarnings("resource")
public static void main(String[] args) throws Exception {
Set<Socket> set = new HashSet<>();
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
Socket socket = serverSocket.accept();
set.add(socket); // anti gc.
}
}
}

gordon.study.socket.basic.wireshark.MockSocketTimeoutThenServerCloseWait_Client.java

public class MockSocketTimeoutThenServerCloseWait_Client {

    public static void main(String[] args) throws Exception {
for (int i = 0; i < 100; i++) {
Thread.sleep(30);
new Thread(new SocketClient()).start();
}
} private static class SocketClient implements Runnable { @Override
public void run() {
try (Socket socket = new Socket()) {
socket.connect(new InetSocketAddress(8888));
socket.setSoTimeout(1000);
socket.getInputStream().read();
} catch (Exception e) {
System.out.println(e);
}
}
}
}

100个线程创建了100个 Socket 连接,在1秒钟后读超时,留下了100个 CLOSE_WAIT 状态的连接。

CLOSE_WAIT 状态的连接并不占用太多操作系统资源,它只是服务器无响应的一种症状,真正的原因还需要自己分析。

大多数情况下是因为客户端超时直接关闭 Socket,同时服务端没能正确关闭 Socket 导致的。可以通过服务端设置读超时、引入心跳检测等方式修复。

真正的问题是客户端为什么会超时。考虑是否超时时间设置太短、业务流中是否有耗时(但不太耗资源)的操作、是否允许了请求排队但是队伍太长导致等待中就超时了。

B. TIME_WAIT

TIME_WAIT 状态会保持 2 * MSL 时间,这是由 TCP 协议规定的。MSL 是指 TCP 报文段生存的最大时间。

在高并发场景下,例如 TPS 1k,如果 MSL 为60秒,那么可能会有 120k 个 TIME_WAIT 状态的连接。这会占用大量系统资源。

TIME_WAIT 一定是主动关闭方才会有的状态,如下图。我们的模拟场景需要由服务方先关闭 Socket。

Java网络编程学习A轮_04_TCP连接异常

gordon.study.socket.basic.wireshark.MockServerTimeWait_Server.java

public class MockServerTimeWait_Server {

    @SuppressWarnings("resource")
public static void main(String[] args) throws Exception {
ServerSocket serverSocket = new ServerSocket(8888);
while (true) {
Socket socket = serverSocket.accept();
socket.close();
}
}
}

gordon.study.socket.basic.wireshark.MockServerTimeWait_Client.java

public class MockServerTimeWait_Client {

    public static void main(String[] args) throws Exception {
for (int i = 0; i < 100; i++) {
Socket socket = new Socket();
socket.connect(new InetSocketAddress(8888));
InputStream is = socket.getInputStream();
while (is.read() != -1) {
}
socket.close();
}
}
}

最简单的解决方案就是让客户端作为主动关闭方。被动关闭方是没有 TIME_WAIT 的烦恼的。