文章目录
前言
Wireshark
是一款功能强大的网络抓包和协议分析工具,用于实时捕获和解析网络通信数据,帮助用户诊断网络问题、分析协议细节或进行安全审计。
本篇将用Wireshark
工具对TCP通信案例进行抓包,分析三报文握手
和四报文挥手
。
一、前置准备
服务端案例工程:
public class BIOServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(1848);
System.out.println("EchoServer started on port 1848");
//阻塞
Socket clientSocket = serverSocket.accept();
handleClient(clientSocket);
}
private static void handleClient(Socket clientSocket) {
try(OutputStream out = clientSocket.getOutputStream();
InputStream in = clientSocket.getInputStream()){
byte[] bytes = new byte[1024];
int len;
while ((len = in.read(bytes))!= -1){
out.write(bytes, 0, len);
out.flush(); // 回显
}
}catch (Exception e){
System.out.println("Client disconnected");
}
}
}
客户端案例工程:
public class BIOClient {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 1848);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
out.write(("Hello Server:" + Thread.currentThread().getName()).getBytes());
out.flush();
byte[] buffer = new byte[1024];
int len = in.read(buffer);
System.out.println("Server Echo: " + new String(buffer, 0, len));
socket.close();
}
}
Wireshark
工具设置,选择Adapter for loopback traffic capture
设置抓包过滤选项,为案例中的端口号1848
:
二、三报文握手过程抓包
案例工程中,三报文握手在java层面体现在客户端
的这一行代码上,创建 Socket 时,会自动连接服务端并完成三次握手;
Socket socket = new Socket("localhost", 1848);
服务端
阻塞在 accept(),等待连接;一旦返回,说明三次握手完成;
Socket clientSocket = serverSocket.accept();
先启动服务端,再启动客户端:
进入断点,准备建立连接
释放客户端的断点,进入服务端的处理消息的逻辑:
此时三报文握手的过程已经结束了,观察Wireshark
2.1、第一次握手
第一次握手的报文10358 371.405938 127.0.0.1 127.0.0.1 TCP 56 49931 → 1848 [SYN] Seq=0 Win=65535 Len=0 MSS=65495 WS=256 SACK_PERM
解释:
字段 | 含义 |
---|---|
10358 |
报文编号,在这次抓包中的编号 |
371.405938 |
抓包开始后的时间戳(秒) |
127.0.0.1 → 127.0.0.1 |
源 IP 和目的 IP(都是 localhost) |
TCP |
协议类型 |
56 |
报文字节数(含 TCP 首部) |
49931 → 1848 |
源端口(49931,客户端临时端口)到目标端口(1848,服务端监听端口) |
[SYN] |
报文类型标志(发起连接请求) |
Seq=0 |
初始序列号,客户端初始发送序号 |
Win=65535 |
窗口大小(接收缓冲区大小) |
Len=0 |
数据长度(SYN 报文本身不带数据) |
MSS=65495 |
最大报文段长度(Maximum Segment Size)是对方发送给我的最大 TCP 有效载荷,协商用 |
WS=256 |
窗口扩大因子(Window Scale),用于扩大窗口大小(65535×256) |
SACK_PERM |
表示支持选择确认(Selective Acknowledgement),提高网络性能 |
2.2、第二次握手
第二次握手的报文10359 371.405972 127.0.0.1 127.0.0.1 TCP 56 1848 → 49931 [SYN, ACK] Seq=0 Ack=1 Win=65535 Len=0 MSS=65495 WS=256 SACK_PERM
的解释:
字段 | 含义 |
---|---|
10359 |
报文编号 |
371.405972 |
时间戳 |
1848 → 49931 |
服务端端口(1848)回应客户端临时端口(49931) |
[SYN, ACK] |
服务端回应连接请求,并确认客户端的 SYN 报文 |
Seq=0 |
服务端自己的初始序列号为 0 |
Ack=1 |
确认号 = 客户端 Seq(0)+ 1,表示收到了客户端的 SYN,对于第一次握手中的seq的确认 |
Win=65535 |
服务端窗口大小 |
Len=0 |
无负载数据,仅握手信息 |
MSS=65495 |
服务端协商的最大段大小 |
WS=256 |
服务端窗口扩展因子 |
SACK_PERM |
服务端也支持 SACK |
2.3、第三次握手
第三次握手的报文10360 371.405991 127.0.0.1 127.0.0.1 TCP 44 49931 → 1848 [ACK] Seq=1 Ack=1 Win=327424 Len=0
的含义:
字段 | 含义 |
---|---|
49931 → 1848 |
客户端端口 → 服务端端口 |
[ACK] |
确认包,确认服务端的 [SYN, ACK] |
Seq=1 |
客户端的下一个发送序列号(上次 SYN 是 Seq=0,这次是 1) |
Ack=1 |
确认号:表示已收到服务端 Seq=0 的 [SYN] 报文(对于服务端seq的确认) |
Win=327424 |
窗口大小(注意,这里乘了窗口缩放因子) |
Len=0 |
仅为控制报文,无负载数据 |
针对原理图,理论联系实际,从上面的握手报文可以看出,除了最关键的SYN,ACK,seq,ack以外,;还体现出了对于TCP参数的协商,例如最大段大小,窗口大小,数据长度等。
三、通信过程抓包
在案例工程中,通信过程体现在客户端发送:
out.write(("Hello Server:" + Thread.currentThread().getName()).getBytes());
服务端接收并回显:
while ((len = in.read(bytes))!= -1){
out.write(bytes, 0, len); // echo 回显
out.flush();
}
报文号 | 时间戳 | 源 → 目标 | 标志位 | Seq/Ack | Len | 说明 |
---|---|---|---|---|---|---|
44379 | 1279.762655 | 49931 → 1848 | [PSH, ACK] | Seq=1 Ack=1 | 17 | 客户端发送“Hello Server…” |
44380 | 1279.762682 | 1848 → 49931 | [ACK] | Seq=1 Ack=18 | 0 | 服务端确认收到了数据 |
44469 | 1283.136551 | 1848 → 49931 | [PSH, ACK] | Seq=1 Ack=18 | 17 | 服务端回显“Hello Server…” |
44470 | 1283.136566 | 49931 → 1848 | [ACK] | Seq=18 Ack=18 | 0 | 客户端确认收到了回显 |
3.1、报文 44379 – 客户端发数据(PSH, ACK)
49931 → 1848 [PSH, ACK] Seq=1 Ack=1 Len=17
:
- 客户端发送数据(17 字节),“Hello Server:main”;
- PSH(Push)表示告诉 TCP 层立即发送,无需等待缓冲区满;
- Seq=1 是三次握手后起始序列号,这里是重点,因为客户端的第三次握手,报文段没有携带数据,不占用seq,所以这次的seq还是从1开始。
- Ack=1 表示仍然确认了服务端的 SYN。
3.2、 报文 44380 – 服务端确认收到数据(ACK)
1848 → 49931 [ACK] Seq=1 Ack=18 Len=0
- 服务端发送确认 ACK,表示已收到客户端发送的 17 字节(1+17=18);
- 这是纯 ACK,不带数据。
3.3、报文 44469 – 服务端回显(PSH, ACK)
1848 → 49931 [PSH, ACK] Seq=1 Ack=18 Len=17
- 服务端将收到的数据原样回显;
- PSH 表示回显数据立即发送;
- Seq=1 是服务端自己的起始序列号;
- Ack=18 表示还在确认客户端数据;
3.4、报文 44470 – 客户端确认收到回显(ACK)
49931 → 1848 [ACK] Seq=18 Ack=18 Len=0
- 客户端收到回显后发出确认 ACK;
- Seq=18 表示客户端准备下次发送的序号(之前发了 17 字节);
- Ack=18 表示收到了服务端的 17 字节(1+17=18)。
四、四报文握手过程抓包
案例工程中,四报文挥手在java层面体现在客户端
的这一行代码上,通信完成关闭连接时,会通知服务端断开连接,进行四报文挥手的流程;
socket.close();
执行断点:
三报文握手和四报文挥手,都是客户端
发起的
报文号 | 时间戳 | 方向 | 标志位 | Len | 说明 |
---|---|---|---|---|---|
46892 | 1380.873591 | 49931 → 1848 | [FIN, ACK] |
0 | 客户端主动断开连接 |
46893 | 1380.873609 | 1848 → 49931 | [ACK] |
0 | 服务端确认客户端 FIN |
46902 | 1380.874091 | 1848 → 49931 | [FIN, ACK] |
0 | 服务端也准备断开连接 |
46903 | 1380.874114 | 49931 → 1848 | [ACK] |
0 | 客户端确认服务端 FIN,连接彻底关闭 |
4.1、第一次挥手
第一次挥手报文段46892 1380.873591 127.0.0.1 127.0.0.1 TCP 44 49931 → 1848 [FIN, ACK] Seq=18 Ack=18 Win=327424 Len=0
的解释:
字段 | 含义 |
---|---|
46892 |
报文编号 |
1380.873591 |
抓包开始以来的时间戳(秒) |
127.0.0.1 → 127.0.0.1 |
客户端 → 服务端 |
TCP |
协议类型 |
44 |
报文长度(含 TCP 头部) |
49931 → 1848 |
客户端临时端口 → 服务端端口 |
[FIN, ACK] |
标志位:客户端请求关闭连接,同时确认服务端数据 |
Seq=18 |
序列号,表示上一次客户端发送的数据是以 Seq=17 结尾的(如 “Hello Server” 17 字节),FIN 占用 1 |
Ack=18 |
确认号:客户端已收到服务端 Seq=0~17 的数据 |
Win=327424 |
客户端窗口大小 |
Len=0 |
无数据,仅控制报文 |
4.2、第二次挥手
第二次挥手报文段46893 1380.873609 127.0.0.1 127.0.0.1 TCP 44 1848 → 49931 [ACK] Seq=18 Ack=19 Win=2161152 Len=0
的解释:
字段 | 含义 |
---|---|
46893 |
报文编号 |
1380.873609 |
时间戳 |
1848 → 49931 |
服务端端口 → 客户端端口 |
[ACK] |
服务端确认客户端关闭请求 |
Seq=18 |
服务端数据已经发到 Seq=17,FIN 尚未发送 |
Ack=19 |
确认号:确认客户端 FIN,占用一个序号(Seq=18 → Ack=19) |
Win=2161152 |
服务端窗口大小 |
Len=0 |
仅 ACK,没有数据 |
4.3、第三次挥手
第三次挥手报文段46902 1380.874091 127.0.0.1 127.0.0.1 TCP 44 1848 → 49931 [FIN, ACK] Seq=18 Ack=19 Win=2161152 Len=0
的解释:
字段 | 含义 |
---|---|
46902 |
报文编号 |
1380.874091 |
时间戳 |
1848 → 49931 |
服务端 → 客户端 |
[FIN, ACK] |
服务端请求关闭,同时 ACK |
Seq=18 |
服务端发出 FIN,占用序号 18(和第二次挥手一致,因为过程中没有发送其他数据) |
Ack=19 |
继续确认客户端的 FIN |
Win=2161152 |
服务端窗口大小 |
Len=0 |
无数据,仅控制位 |
4.4、第四次挥手
第四次挥手报文段46903 1380.874114 127.0.0.1 127.0.0.1 TCP 44 49931 → 1848 [ACK] Seq=19 Ack=19 Win=327424 Len=0
的解释:
字段 | 含义 |
---|---|
46903 |
报文编号 |
1380.874114 |
时间戳 |
49931 → 1848 |
客户端 → 服务端 |
[ACK] |
客户端确认服务端的关闭 |
Seq=19 |
客户端上一次是 Seq=18,FIN 占 1,+1 到 19 |
Ack=19 |
表示已经收到服务端的 FIN(Seq=18) |
Win=327424 |
客户端窗口大小 |
Len=0 |
无数据 |