Java网络编程学习A轮_03_抓包分析TCP四次挥手
参考资料:
http://www.jellythink.com/archives/705
示例代码:
https://github.com/gordonklg/study,socket module
A. TCP 四次挥手
下图描述了 TCP 整个生命周期从建立连接到断开连接的逻辑:
下面的测试代码完美模拟了上图
gordon.study.socket.basic.wireshark.TcpLifecycle.java
1 public class TcpLifecycle { 2 3 @SuppressWarnings("resource") 4 public static void main(String[] args) throws Exception { 5 new Thread(new Runnable() { 6 @Override 7 public void run() { 8 try { 9 ServerSocket serverSocket = new ServerSocket(8888); 10 Socket socket = serverSocket.accept(); 11 socket.getInputStream().read(); 12 socket.close(); 13 } catch (Exception e) { 14 } 15 } 16 }).start(); 17 18 Socket socket = new Socket(); 19 socket.connect(new InetSocketAddress(8888)); 20 Thread.sleep(3000); 21 socket.getOutputStream().write('a'); 22 socket.close(); 23 } 24 }
WireShark 的截图如下,解释都是多余的。
B. Socket timeout
Java 中的 socket 超时只针对读操作。
gordon.study.socket.basic.wireshark.ClientSocketTimeout.java
1 public class ClientSocketTimeout { 2 3 @SuppressWarnings("resource") 4 public static void main(String[] args) throws Exception { 5 new Thread(new Runnable() { 6 @Override 7 public void run() { 8 try { 9 ServerSocket serverSocket = new ServerSocket(8888); 10 serverSocket.accept(); 11 while (true) { 12 Thread.sleep(1000); 13 } 14 } catch (Exception e) { 15 } 16 } 17 }).start(); 18 19 try (Socket socket = new Socket()) { 20 socket.connect(new InetSocketAddress(8888)); 21 socket.setSoTimeout(1000); 22 socket.getInputStream().read(); 23 } 24 } 25 }
由于第21行代码设置了 socket timeout,第22行代码中 read 方法最多阻塞1秒钟,如果无法从流中读取数据,则抛出 java.net.SocketTimeoutException。
抛出异常后,主线程退出,由于使用了 try-with-resource,退出前会关闭客户端 socket,但是服务端永远在睡大觉,不会关闭 socket,所以整个 TCP 连接并没有完全关闭。
从下边的 WireShark 截图可知,TCP四次挥手只走了一半(No.4 & No.5),服务端没有调用 close 方法,所以服务端处于 CLOSE_WAIT 状态,而客户端处于 FIN_WAIT_2 状态,下面的 CMD 截图可以证明。
而 120 秒后(WireShark截图 No.228),FIN_WAIT_2 状态的 55421 端口主动向 8888 端口发送 RST 重置请求,最终两个异常状态都消失了。这应该是操作系统层面做的。这部分没有搞明白,放到B轮再研究。