Java+tcpdump分析四次挥手

431 阅读2分钟

实操

public static void main(String[] args) throws IOException, InterruptedException {
    ServerSocket serverSocket = new ServerSocket(8899);
    // 故意的睡眠
    Thread.sleep(Long.MAX_VALUE);

    for (; ; ) {
        Socket acceptSocket = serverSocket.accept();
        System.out.println(acceptSocket);

        for (; ; ) {
            int read = acceptSocket.getInputStream().read();

            if (read == -1) {
                acceptSocket.close();
                System.out.println(acceptSocket + " 下线");
                break;
            }
        }
    }
}
  • 如上代码,我起了一个java-server,在执行new ServerSocket(8899)之后,就开始睡眠了。不过在这时操作系统已经产生了一个listen socket
➜  ~ netstat -an | grep 8899
tcp46      0      0  *.8899                 *.*                    LISTEN
  • 此时我用nc去连这个Java server 通过tcpdump发现三次握手了
12:35:54.574451 IP node01.53646 > 192.168.31.120.ospf-lite: Flags [S], seq 1325316749, win 14600, options [mss 1460,sackOK,TS val 65057503 ecr 0,nop,wscale 6], length 0
12:35:54.574964 IP 192.168.31.120.ospf-lite > node01.53646: Flags [S.], seq 3221323920, ack 1325316750, win 64240, options [mss 1460], length 0
12:35:54.574977 IP node01.53646 > 192.168.31.120.ospf-lite: Flags [.], ack 3221323921, win 14600, length 0
  • 这时候再去看下端口状态,已经建立好连接了。不过此时Java server在睡觉
➜  ~ netstat -an | grep 8899
tcp4       0      0  192.168.31.120.8899    192.168.31.120.63723   ESTABLISHED
tcp4       0      0  192.168.31.120.63723   192.168.31.120.8899    ESTABLISHED
tcp46      0      0  *.8899
  • 这时候把nc Ctrl-C,关掉客户端进程,通过抓包可以发现四次挥手中的前两次。
12:36:13.475971 IP node01.53646 > 192.168.31.120.ospf-lite: Flags [F.], seq 1325316750, ack 3221323921, win 14600, length 0
12:36:13.476368 IP 192.168.31.120.ospf-lite > node01.53646: Flags [.], ack 1325316751, win 64239, length 0
  • 在看下端口状态
➜  ~ netstat -an | grep 8899
tcp4       0      0  192.168.31.120.8899    192.168.31.120.63723   CLOSE_WAIT
tcp4       0      0  192.168.31.120.63723   192.168.31.120.8899    FIN_WAIT_2
tcp46      0      0  *.8899                 *.*                    LISTEN

分析

FIN_WAIT_2&CLOSE_WAIT

结合这两个状态以及抓包结果进行分析。

  • 客户端发出了FIN,服务端收到并且回复了ACK, 由于服务器阻塞,导致服务端没能发出 第三次挥手的FIN 并且状态就是CLOSED-WAIT了。客户端也在等服务端发FIN,没等来 所以状态是FIN-WAIT2

java-server如何知道client下线呢

服务器端拿到输入流进行read。当客户端断开连接后,服务器端read一直有返回值是-1

ServerSocket serverSocket = new ServerSocket(8899);
for (; ; ) {
    Socket acceptSocket = serverSocket.accept();
    System.out.println(acceptSocket);
    for (; ; ) {
        int read = acceptSocket.getInputStream().read();
        System.out.println(read);
    }
}

此时这个-1是否恰恰就是客户端断开连接的标志呢?server得知这个消息后需要进行

int read = acceptSocket.getInputStream().read();
if (read == -1) {
    acceptSocket.close();
}

执行close,服务端发送了FIN,客户端也随之回复了ACK。四次挥手完毕。

分析过程