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

参考资料:
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。

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 的烦恼的。

posted @ 2017-08-07 11:21  首夜盲毒预言家  阅读(253)  评论(0编辑  收藏  举报