预先准备
抓包工具 Wireshark
1 | wireshark -v |
如果未安装,可按照下方步骤安装:
1 | 安装 |
项目管理工具 Gradle
1 | gradle -v |
如果未安装,可按照下方步骤安装:
1 | 安装 sdkman |
核心代码
本文的所有代码已上传至 GitHub,其中将握手和挥手分开测试是为了防止客户端在发送过程中,服务端就提前关闭了,导致挥手的部分包可能丢掉。
三次握手-服务端
建立 socket 服务器,并在 8000 端口(可先用sudo netstat -ntlp | grep 8000 查看端口是否已被占用)监听,核心代码如下:
1 | ServerSocket socket = new ServerSocket(8000); |
三次握手-客户端
连接上方服务器,并发送数据,核心代码如下:
1 | Socket socket = new Socket("127.0.0.1", 8000); |
四次挥手-服务端
1 | ServerSocket serverSocket = new ServerSocket(8000); |
四次挥手-客户端
1 | Socket socket = new Socket("127.0.0.1", 8000); |
wireshark 抓包分析
捕获过滤器
1 | host 127.0.0.1 and port 8000 |
字段意义

| 标志位 | 作用 |
|---|---|
| URG | 表示某一位需要被优先处理 |
| ACK | 表示确认对方的请求 |
| PSH | 表示有数据传输 |
| RST | 表示请求重置连接 |
| SYN | 表示请求建立连接 |
| FIN | 表示请求断开连接 |
| — | — |
| seq | 序列号,用来标记数据段的顺序 |
| ack | 确认号,期待收到对方下一个报文段的第一个数据字节的序号 |
TCP 三次握手分析
抓到的包:
对应过程:
文字描述:
客户端发送 SYN 报文(请求与服务器连接),并置发送序号为 x
服务端发送 SYN + ACK 报文,并置发送序号为 y,确认序号为 x+1
客户端发送 ACK 报文,并置发送序号为 x+1,确认序号为 y+1
TCP 四次挥手分析
抓到的包:
对应过程:
文字描述:
客户端发送 FIN 报文(请求断开连接),并置发送序号为u
服务端发送 ACK 报文,并置发送序号为 v,确认序号为 u+1
服务端发送 FIN + ACK 报文,并置发送序号为 w,确认序号为 u+1
客户端发送 ACK 报文,并置发送序号为 u+1,确认序号为 w+1
常见问题
为什么连接的时候是三次握手,关闭的时候却是四次挥手?
因为当服务端收到客户端的 SYN 连接请求报文后,可以直接发送 SYN+ACK 报文。其中 ACK 报文是用来应答的,SYN 报文是用来同步的。但是关闭连接时,当服务端收到 FIN 报文时,可能并不会立即关闭 SOCKET,所以只能先回复一个 ACK 报文,告诉客户端已收到 FIN 报文。但只有等到服务端所有的报文都发送完了,我才能发送 FIN 报文,因此不能像连接时一样直接马上发送 FIN + ACK,所以需要四步挥手。
为什么不能用两次握手进行连接?
三次握手完成两个重要的功能:
①双方做好发送数据的准备工作;
②允许双方就初始序列号进行协商,这个序列号在握手过程中被发送和确认。
如果改为两次握手,则可能发生死锁。
举个例子,假定客户端给服务端发送一个连接请求分组,服务端收到了这个分组,并发送了确认应答分组。按照两次握手的协定,服务端认为连接已经成功地建立了,可以开始发送数据分组。可是,客户端在服务端的应答分组在传输中被丢失的情况下,将不知道服务端是否已准备好,不知道服务端建立什么样的序列号,客户端甚至怀疑服务端是否收到自己的连接请求分组。在这种情况下,客户端认为连接还未建立成功,将忽略服务端发来的任何数据分组,只等待连接确认应答分组。而服务端在发出的分组超时后,重复发送同样的分组。这样就形成了死锁。
参考文章:
[1] 使用wireshark抓包读懂tcp三次握手,四次挥手
[2] TCP的三次握手与四次挥手理解及面试题(很全面)
[3] 使用WireShark分析TCP的三次握手过程