一、分析连接过程
root@225f19598cc3:/# tcpdump -v -n
tcpdump: listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
# 客户端 → 服务端:SYN 包。客户端发起连接请求,标志位为 [S](SYN),表示同步请求,初始序列号 seq=167702838,MSS(最大报文段长度)为 1460 字节
# 协议类型和 IP 头部信息
# tos 0x0:服务类型
# ttl 64:生存时间.每经过一个路由器减 1,减到 0 时丢弃。此处为 64,说明该数据包最多还能经过 64 跳
# id 55449:IP ID(标识符),用于唯一标识当前主机发送的数据报,主要用于分片重组。
# offset 0:分片偏移量,表示该数据包在原始数据报中的位置。这里为 0,表示这是第一个(也是唯一一个)分片。
# flags [DF]:标志位,DF 表示 Don't Fragment(不分片)。TCP 通常会设置此标志以避免分片。
# proto TCP (6):上层协议为 TCP,协议号为 6。
# length 60:P 总长度为 60 字节,包括 IP 头(20 字节) + TCP 头(20 字节)+ 可能的选项部分(20 字节)。
# Flags [S]:[S] 表示 SYN 标志位被置 1。
# seq 167702838:Sequence Number(序列号):本次发送的第一个字节的编号,用于保证顺序可靠传输。初始值随机生成,这里是 167702838。
# win 64240:Window Size(窗口大小):接收方当前可以接收的数据量,单位是字节。表示客户端目前允许服务端发送最多 64240 字节 的数据而不必等待确认。
# options:TCP 选项(Options)。
# length 0:表示 TCP 数据部分长度为 0,这是一个纯控制包(SYN),没有携带应用层数据。
09:25:52.414604 IP (tos 0x0, ttl 64, id 55449, offset 0, flags [DF], proto TCP (6), length 60)
172.18.0.1.43614 > 172.18.0.2.80: Flags [S], cksum 0x5856 (incorrect -> 0x7098), seq 167702838, win 64240, options [mss 1460,sackOK,TS val 2618638934 ecr 0,nop,wscale 7], length 0
# 服务端 → 客户端:SYN-ACK 包。服务端回应连接请求,标志位为 [S.](SYN + ACK),序列号 seq=4227940362,确认号 ack=167702839(即客户端初始序列号 + 1)。
09:25:52.414621 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
172.18.0.2.80 > 172.18.0.1.43614: Flags [S.], cksum 0x5856 (incorrect -> 0x5e4c), seq 4227940362, ack 167702839, win 65160, options [mss 1460,sackOK,TS val 476034615 ecr 2618638934,nop,wscale 7], length 0
# 客户端 → 服务端:ACK 包。客户端发送确认包,标志位为 [.](仅 ACK),确认号 ack=1,表示对服务端序列号的确认,此时 TCP 连接已建立。
09:25:52.414638 IP (tos 0x0, ttl 64, id 55450, offset 0, flags [DF], proto TCP (6), length 52)
172.18.0.1.43614 > 172.18.0.2.80: Flags [.], cksum 0x584e (incorrect -> 0x89ab), ack 1, win 502, options [nop,nop,TS val 2618638934 ecr 476034615], length 0
# 客户端 → 服务端:HTTP GET 请求。客户端发送 HTTP GET 请求,标志位为 [P.](PSH + ACK),表示有数据推送,数据内容是 HTTP 请求头,请求根路径 /。
# IP length = 126:表示整个 IP 包的长度为 126 字节。表示整个 IP 包的长度为 126 字节。IP 头部(通常 20 字节),TCP 头部(通常 20 字节 + 选项如 TS),数据部分(74 字节)。
# TCP length = 74:表示 TCP 的数据部分(即 HTTP 请求头)长度是 74 字节。
09:25:52.414679 IP (tos 0x0, ttl 64, id 55451, offset 0, flags [DF], proto TCP (6), length 126)
172.18.0.1.43614 > 172.18.0.2.80: Flags [P.], cksum 0x5898 (incorrect -> 0x864f), seq 1:75, ack 1, win 502, options [nop,nop,TS val 2618638934 ecr 476034615], length 74: HTTP, length: 74
GET / HTTP/1.1
Host: 172.18.0.2
User-Agent: curl/7.81.0
Accept: */*
# 服务端 → 客户端:ACK 确认。服务端确认收到客户端的数据。
09:25:52.414684 IP (tos 0x0, ttl 64, id 37395, offset 0, flags [DF], proto TCP (6), length 52)
172.18.0.2.80 > 172.18.0.1.43614: Flags [.], cksum 0x584e (incorrect -> 0x895a), ack 75, win 509, options [nop,nop,TS val 476034615 ecr 2618638934], length 0
# 服务端 → 客户端:HTTP 响应头。内容长度为 13 字节,说明正文很小。
09:25:52.414809 IP (tos 0x0, ttl 64, id 37396, offset 0, flags [DF], proto TCP (6), length 287)
172.18.0.2.80 > 172.18.0.1.43614: Flags [P.], cksum 0x5939 (incorrect -> 0xec06), seq 1:236, ack 75, win 509, options [nop,nop,TS val 476034615 ecr 2618638934], length 235: HTTP, length: 235
HTTP/1.1 200 OK
Server: nginx/1.24.0
Date: Mon, 14 Jul 2025 09:25:52 GMT
Content-Type: text/html
Content-Length: 13
Last-Modified: Tue, 08 Jul 2025 09:16:33 GMT
Connection: keep-alive
ETag: "686ce1f1-d"
Accept-Ranges: bytes
# 客户端 → 服务端:ACK 确认。
09:25:52.414829 IP (tos 0x0, ttl 64, id 55452, offset 0, flags [DF], proto TCP (6), length 52)
172.18.0.1.43614 > 172.18.0.2.80: Flags [.], cksum 0x584e (incorrect -> 0x8877), ack 236, win 501, options [nop,nop,TS val 2618638934 ecr 476034615], length 0
# 服务端 → 客户端:HTTP 响应体(正文)。服务端发送响应正文,共 13 字节。
09:25:52.414854 IP (tos 0x0, ttl 64, id 37397, offset 0, flags [DF], proto TCP (6), length 65)
172.18.0.2.80 > 172.18.0.1.43614: Flags [P.], cksum 0x585b (incorrect -> 0x2c6b), seq 236:249, ack 75, win 509, options [nop,nop,TS val 476034615 ecr 2618638934], length 13: HTTP
# 客户端 → 服务端:ACK 确认。客户端再次确认收到全部数据。
09:25:52.414866 IP (tos 0x0, ttl 64, id 55453, offset 0, flags [DF], proto TCP (6), length 52)
172.18.0.1.43614 > 172.18.0.2.80: Flags [.], cksum 0x584e (incorrect -> 0x886a), ack 249, win 501, options [nop,nop,TS val 2618638934 ecr 476034615], length 0
# 客户端 → 服务端:FIN 包。客户端发送 FIN 包,表示要关闭连接,标志位为 [F.](FIN + ACK),序列号为 75,表示这是最后一个数据字节。
09:25:52.414960 IP (tos 0x0, ttl 64, id 55454, offset 0, flags [DF], proto TCP (6), length 52)
172.18.0.1.43614 > 172.18.0.2.80: Flags [F.], cksum 0x584e (incorrect -> 0x8869), seq 75, ack 249, win 501, options [nop,nop,TS val 2618638934 ecr 476034615], length 0
# 服务端 → 客户端:ACK 确认。服务端回应一个 ACK,并发送自己的 FIN 包,标志位为 [F.](FIN + ACK),确认号为 76,表示已接收客户端的 FIN。
09:25:52.414991 IP (tos 0x0, ttl 64, id 37398, offset 0, flags [DF], proto TCP (6), length 52)
172.18.0.2.80 > 172.18.0.1.43614: Flags [F.], cksum 0x584e (incorrect -> 0x8860), seq 249, ack 76, win 509, options [nop,nop,TS val 476034615 ecr 2618638934], length 0
# 客户端 → 服务端:ACK 确认。客户端发送最终的 ACK,完成四次挥手,连接完全关闭。
09:25:52.415010 IP (tos 0x0, ttl 64, id 55455, offset 0, flags [DF], proto TCP (6), length 52)
172.18.0.1.43614 > 172.18.0.2.80: Flags [.], cksum 0x584e (incorrect -> 0x8868), ack 250, win 501, options [nop,nop,TS val 2618638934 ecr 476034615], length 0
二、TCP 连接全过程 seq 与 ack 变化表
在第一次挥手之后,如果被动方没有数据要发给主动方。第二和第三次挥手是有可能合并传输的,服务端把 ACK 和 FIN 放在一个包里(即合并了第 2 和第 3 步)。这样就出现了三次挥手。
方向 | 标志位 | seq 值(起始) |
seq 范围 |
数据长度 | ack 值 |
seq 来源说明 |
ack 来源说明 |
---|---|---|---|---|---|---|---|
C→S | [S] |
167702838 |
- | 0 | - | 客户端初始 SEQ(随机生成) | 无 ACK(SYN 包) |
S→C | [S.] |
4227940362 |
- | 0 | 167702839 |
服务端初始 SEQ(随机生成) | 客户端 SEQ + 1(SYN 占一个字节) |
C→S | [.] |
167702839 |
- | 0 | 4227940363 |
上一次客户端 SEQ + 1(SYN 占一个字节) | 服务端 SEQ + 1(SYN 占一个字节) |
C→S | [P.] |
1 |
1:75 |
74 | 1 |
上一次客户端 SEQ(167702839)后开始新数据流,起始为 1(可能是相对序号) | 上一次服务端 SEQ(4227940362)+1 后已确认 |
S→C | [.] |
1 |
- | 0 | 75 |
上一次服务端 SEQ(4227940362)后开始新数据流,起始为 1(相对序号) | 客户端上一次 SEQ(1)+ 数据长度(74) |
S→C | [P.] |
1 |
1:236 |
235 | 75 |
继续使用当前 SEQ | 保持不变 |
C→S | [.] |
75 |
- | 0 | 236 |
上一次客户端 SEQ(1)+ 数据长度(74) | 服务端上一次 SEQ(1)+ 数据长度(235) |
S→C | [P.] |
236 |
236:249 |
13 | 75 |
上一次服务端 SEQ(1)+ 数据长度(235) | 保持不变 |
C→S | [.] |
75 |
- | 0 | 249 |
保持不变 | 服务端上一次 SEQ(236)+ 数据长度(13) |
C→S | [F.] |
75 |
- | 0 | 249 |
保持不变 | 保持不变 |
S→C | [.] |
249 |
- | 0 | 76 |
上一次服务端 SEQ(236)+ 数据长度(13) | 客户端 FIN 的 SEQ(75)+1 |
S→C | [F.] |
249 |
- | 0 | 76 |
保持不变 | 保持不变 |
C→S | [.] |
76 |
- | 0 | 250 |
客户端 FIN 的 SEQ(75)+1 | 服务端 FIN 的 SEQ(249)+1 |
1.初始连接(三次握手)
SYN 报文**会占用一个序列号(即使没有数据)
- 客户端发送 SYN:
seq = 167702838
- 服务端回复 SYN/ACK:
seq = 4227940362
,ack = 167702838 + 1 = 167702839
- 客户端最后 ACK:
seq = 167702838 + 1 = 167702839
,ack = 4227940362 + 1 = 4227940363
2.数据传输阶段
HTTP 请求(GET /)
- 客户端发送数据:
seq = 1
(可能基于连接建立后的相对序号) - 数据长度:74 字节 → 下一个期望收到的序号是
1 + 74 = 75
- 服务端返回 ACK:
ack = 75
服务端响应(HTTP 200 OK)
-
第一包数据:
seq = 1
, 长度 235 → 下一个是1 + 235 = 236
-
客户端返回 ACK:
ack = 236
-
第二包数据:
seq = 236
, 长度 13 → 下一个是236 + 13 = 249
-
客户端返回 ACK:
ack = 249
3.四次挥手
客户端发送 FIN
- 当前客户端最后一个数据 SEQ 是
75
(上次发送的是 HTTP 请求结束位置) - FIN 标志也占一个 SEQ → 所以发送
seq = 75
,Flags [F.]
服务端回应 ACK
- 确认客户端 FIN:
ack = 75 + 1 = 76
服务端发送 FIN
- 当前服务端最后一个数据 SEQ 是
249
(上一个数据包结束在 249) - 发送
seq = 249
,Flags [F.]
客户端回应 ACK
- 确认服务端 FIN:
ack = 249 + 1 = 250
类型 | seq 增长方式 | ack 计算方式 |
---|---|---|
普通数据段 | 当前 seq + 数据长度 |
对方 seq + 数据长度 |
SYN/FIN 标志 | 占用 1 个 seq | 对方 seq + 1 |
纯 ACK(无数据) | 不增长 | 对方 seq + 数据长度(或 +1 若有 SYN/FIN) |
三、tcpdump 参数
tcpdump -i eth0 -nn port 80 -A -s 0
tcpdump -i eth0 -nn port 80 -X -s 0
tcpdump -i any -nn port 80 -X -s 0
如果你没有加上 -X、-xx、-A 等参数,tcpdump 默认不会打印 payload(正文)内容
- -i eth0: 指定网卡
- -i any:监听所有网卡
- -nn: 不解析主机名和服务名
- port 80: 只抓 HTTP 流量
- -A: 以 ASCII 形式显示数据内容
- -s 0: 抓取完整包(不受 snaplen 截断)
四、常见的 TCP Flags 标志位
Flag | 缩写 | 含义 |
---|---|---|
SYN | [S] |
同步序号,用于发起一个连接。 |
ACK | [.] |
确认序号有效。通常单独显示为点(. ),但在其他标志存在时会明确写出。 |
FIN | [F] |
发送端已经完成数据发送,请求关闭连接。 |
RST | [R] |
重置连接。用于异常终止连接。 |
PSH | [P] |
推送功能,提示接收方尽快将数据提交给应用程序,而不是等待更多的数据到来。 |
URG | [U] |
紧急指针字段有效,表示该数据段中有紧急数据。 |
ECE | [E] |
ECN-Echo,用于显式拥塞通知机制。 |
CWR | [C] |
拥塞窗口减少,用于显式拥塞通知机制。 |