TCP协议详解——三次握手、四次挥手与连接状态

TCP协议详解------三次握手、四次挥手与连接状态

本文基于华为云 ECS 实战环境,在 server-3(192.168.0.23)上通过 tcpdump 抓包、ss 连接状态查看、sysctl 内核参数读取等手段,深入剖析 TCP 协议的核心机制。所有数据均来自真实生产环境抓包,完整还原 TCP 连接的建立、数据传输与断开全过程。

一、TCP协议概述

TCP(Transmission Control Protocol,传输控制协议)是互联网基石协议之一,位于 OSI 模型第四层------传输层。与 UDP 不同,TCP 具有以下三大核心特性:

特性 说明 对比 UDP
面向连接 通信前必须通过三次握手建立连接 UDP 无连接,直接发送
可靠传输 通过序号、确认、重传保证数据可达 UDP 尽力而为,不保证可靠
字节流 面向字节流,无消息边界 UDP 面向报文,保留消息边界

此外,TCP 还提供流量控制(滑动窗口)、拥塞控制(cubic/reno 算法)、连接管理(状态机)等高级机制,确保在各种网络条件下高效、公平地传输数据。

二、TCP报文头结构

TCP 报文头标准长度为 20 字节(不含选项),其结构如下:

scss 复制代码
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|          源端口 (16bit)       |       目的端口 (16bit)        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                        序列号 (32bit)                         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                     确认号 (32bit)                            |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  数据偏移 | 保留 |U|A|P|R|S|F|          窗口 (16bit)          |
|   (4bit)  | (3b) |R|C|S|S|Y|I|                                 |
|           |      |G|K|H|T|N|N|                                 |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|       校验和 (16bit)          |       紧急指针 (16bit)         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    选项 (可变长度)                             |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

各关键字段含义:

字段 长度 说明
源端口 16 bit 发送方端口
目的端口 16 bit 接收方端口
序列号 (seq) 32 bit 标识本报文段数据第一个字节的序号
确认号 (ack) 32 bit 期望收到对方下一个报文段的第一个字节序号
标志位 6 bit URG/ACK/PSH/RST/SYN/FIN
窗口 (win) 16 bit 接收窗口大小,用于流量控制
校验和 16 bit 检验报文头和数据的完整性

tcpdump 输出中常见的标志位缩写:[S]=SYN,[.]=ACK,[P.]=PSH+ACK,[F.]=FIN+ACK,[S.]=SYN+ACK,[R.]=RST+ACK。

三、TCP三次握手

3.1 抓包实战

在 server-3(192.168.0.23)上对 server-1(192.168.0.201:80)发起 HTTP 请求,同时用 tcpdump 抓取完整握手过程:

bash 复制代码
# 在 server-3 上抓包
tcpdump -i eth0 -nn -v -S 'host 192.168.0.201 and tcp port 80' -c 20

抓包结果(真实输出):

ini 复制代码
12:05:10.465060 IP (tos 0x0, ttl 64, id 39911, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.0.23.42778 > 192.168.0.201.80: Flags [S], cksum 0x825f (incorrect -> 0x6492),
    seq 619644684, win 64240, options [mss 1460,sackOK,TS val 18321871 ecr 0,nop,wscale 7], length 0

12:05:10.465230 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto TCP (6), length 60)
    192.168.0.201.80 > 192.168.0.23.42778: Flags [S.], cksum 0xbd35 (correct),
    seq 991996670, ack 619644685, win 65160,
    options [mss 1460,sackOK,TS val 4061121412 ecr 18321871,nop,wscale 7], length 0

12:05:10.465250 IP (tos 0x0, ttl 64, id 39912, offset 0, flags [DF], proto TCP (6), length 52)
    192.168.0.23.42778 > 192.168.0.201.80: Flags [.], cksum 0x8257 (incorrect -> 0xe894),
    ack 991996671, win 502, options [nop,nop,TS val 18321871 ecr 4061121412], length 0

3.2 逐包解析

第一个包:SYN(客户端 → 服务端)

  • Flags [S]:SYN 标志位置 1,表示发起连接请求
  • seq 619644684:客户端初始序列号(ISN),由内核随机生成
  • win 64240:客户端宣告的接收窗口大小
  • options [mss 1460,sackOK,TS val 18321871 ecr 0,nop,wscale 7]
    • mss 1460:最大段大小,表示客户端能接收的每个 TCP 段最大数据长度
    • sackOK:支持选择性确认(Selective ACK)
    • TS val 18321871 ecr 0:时间戳选项,用于计算 RTT
    • wscale 7:窗口缩放因子为 7,实际窗口 = 64240 × 2^7 = 8,222,720 字节

第二个包:SYN-ACK(服务端 → 客户端)

  • Flags [S.]:SYN 和 ACK 同时置 1
  • seq 991996670:服务端初始序列号
  • ack 619644685:确认号 = 客户端 ISN + 1 = 619644684 + 1
  • win 65160:服务端宣告的接收窗口
  • 同样携带 mss 1460wscale 7 选项

第三个包:ACK(客户端 → 服务端)

  • Flags [.]:仅 ACK 标志位置 1
  • ack 991996671:确认号 = 服务端 ISN + 1 = 991996670 + 1
  • win 502:窗口缩放后的通告值(502 × 2^7 = 64,256 字节)
  • 连接建立完成,进入 ESTABLISHED 状态

3.3 时序图

ini 复制代码
  client (192.168.0.23:42778)              server (192.168.0.201:80)
       |                                        |
       |------- SYN, seq=619644684 ------------>|
       |        win=64240, mss=1460, wscale=7   |
       |        (客户端进入 SYN_SENT)             |
       |                                        |
       |<------ SYN+ACK, seq=991996670 ---------|
       |        ack=619644685, win=65160        |
       |        (服务端进入 SYN_RECV)             |
       |                                        |
       |------- ACK, ack=991996671 ------------>|
       |        win=502                          |
       |        (双方进入 ESTABLISHED)            |
       |                                        |
       |======= 连接建立,可传输数据 =============|
       |                                        |
       |------- PSH+ACK, GET / HTTP/1.1 ------->|
       |        seq=619644685, length=76         |
       |                                        |

三次握手的本质是双方互相确认对方的接收能力正常,并协商初始序列号。之所以是"三次"而非"两次",是因为第二次握手时服务端的 SYN 和 ACK 合并为一个包发送,减少了通信开销。

四、TCP四次挥手

4.1 抓包实战

同一抓包会话中,连接断开时的四次挥手(以 server-1 到 server-3 的代理连接为例):

ini 复制代码
12:05:10.465761 IP (tos 0x0, ttl 64, id 24000, offset 0, flags [DF], proto TCP (6), length 52)
    192.168.0.23.80 > 192.168.0.201.55960: Flags [F.], cksum 0x8257 (incorrect -> 0xab1c),
    seq 3618244973, ack 2627313244, win 508, options [nop,nop,TS val 18321872 ecr 4061121412], length 0

12:05:10.465830 IP (tos 0x0, ttl 64, id 20550, offset 0, flags [DF], proto TCP (6), length 52)
    192.168.0.201.55960 > 192.168.0.23.80: Flags [.], cksum 0xab23 (correct),
    ack 3618244973, win 501, options [nop,nop,TS val 4061121413 ecr 18321872], length 0

12:05:10.465856 IP (tos 0x0, ttl 64, id 20551, offset 0, flags [DF], proto TCP (6), length 52)
    192.168.0.201.55960 > 192.168.0.23.80: Flags [F.], cksum 0xab21 (correct),
    seq 2627313244, ack 3618244974, win 501, options [nop,nop,TS val 4061121413 ecr 18321872], length 0

12:05:10.465860 IP (tos 0x0, ttl 64, id 24001, offset 0, flags [DF], proto TCP (6), length 52)
    192.168.0.23.80 > 192.168.0.201.55960: Flags [.], cksum 0x8257 (incorrect -> 0xab1a),
    ack 2627313245, win 508, options [nop,nop,TS val 18321872 ecr 4061121413], length 0

4.2 时序图

ini 复制代码
  主动关闭方 (192.168.0.23:80)           被动关闭方 (192.168.0.201:55960)
       |                                        |
       |------- FIN+ACK, seq=3618244973 ------->|
       |        ack=2627313244                   |
       |        (进入 FIN_WAIT_1)                |
       |                                        |
       |<------ ACK, ack=3618244973 ------------|
       |        (进入 FIN_WAIT_2)                |
       |        (对端进入 CLOSE_WAIT)             |
       |                                        |
       |<------ FIN+ACK, seq=2627313244 --------|
       |        ack=3618244974                   |
       |        (对端进入 LAST_ACK)               |
       |                                        |
       |------- ACK, ack=3627313245 ------------>|
       |        (进入 TIME_WAIT, 等待 2MSL)      |
       |        (对端进入 CLOSED)                 |
       |                                        |
       |======== 连接完全关闭 ===================|

四次挥手之所以是四次而非三次,是因为 TCP 是全双工通信------关闭连接时,每个方向都需要独立关闭。FIN 只表示"我没有数据要发了",但仍然可以接收数据。因此两个方向的 FIN 需要分别发送和确认。

4.3 3包挥手现象

值得注意的是,在原始客户端连接(192.168.0.23.42778 → 192.168.0.201:80)的断开过程中,抓包显示的是3包挥手:

ini 复制代码
12:05:10.465931 - 192.168.0.23.42778 > 192.168.0.201.80: Flags [F.], seq 619644761
12:05:10.466011 - 192.168.0.201.80 > 192.168.0.23.42778: Flags [F.], seq 991997009, ack 619644762
12:05:10.466013 - 192.168.0.23.42778 > 192.168.0.201.80: Flags [.], ack 991997010

服务端将 ACK 和 FIN 合并为一个包返回,从而将四次挥手压缩为三次。这在服务端没有待发送数据时很常见。

五、TCP连接状态

5.1 状态机总览

状态 说明 触发条件
LISTEN 监听等待连接 服务端调用 listen()
SYN_SENT 已发送SYN 客户端调用 connect()
SYN_RECV 已收到SYN并发送SYN+ACK 服务端收到SYN
ESTABLISHED 连接已建立 三次握手完成
FIN_WAIT_1 已发送FIN 主动关闭方调用 close()
FIN_WAIT_2 收到对端ACK 半关闭状态
TIME_WAIT 等待2MSL 主动关闭方收到最后FIN的ACK
CLOSE_WAIT 收到对端FIN 被动关闭方等待应用调用 close()
LAST_ACK 已发送FIN 被动关闭方等待最后ACK
CLOSED 连接完全关闭 最终状态

5.2 实战状态查看

在 server-3 上执行 ss -tan 查看当前所有 TCP 连接状态:

bash 复制代码
ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn

真实输出:

css 复制代码
      9 TIME-WAIT
      7 LISTEN
      2 ESTAB
      1 State

可以看到,系统中有 9 个 TIME-WAIT 连接(之前的短连接关闭后遗留)、7 个 LISTEN 端口和 2 个 ESTABLISHED 连接。

查看 TIME-WAIT 状态的具体连接:

css 复制代码
Recv-Q Send-Q Local Address:Port   Peer Address:Port Process
0      0       192.168.0.23:39826 192.168.0.201:80
0      0       192.168.0.23:39862 192.168.0.201:80
0      0       192.168.0.23:39846 192.168.0.201:80
0      0       192.168.0.23:80    192.168.0.201:61610

这些 TIME-WAIT 连接都是与 192.168.0.201 的 HTTP 通信结束后遗留的。大量 TIME-WAIT 是短连接场景的典型现象。

查看 ESTABLISHED 状态的连接:

css 复制代码
Recv-Q Send-Q Local Address:Port     Peer Address:Port Process
0      0       192.168.0.23:46292  100.125.12.110:33554
0      0       192.168.0.23:22     114.116.247.146:55650

当前有两条活跃连接:一条 SSH 会话(端口22)和一条内网管理通道。

5.3 TIME_WAIT的意义

TIME_WAIT 状态持续 2MSL(最大报文段生存时间),有两个核心作用:

  1. 确保最后一个 ACK 到达对端:如果最后发出的 ACK 丢失,对端会重发 FIN,本端可以再次发送 ACK
  2. 防止旧连接的报文干扰新连接:等待 2MSL 后,网络中残留的旧报文必然过期消失

六、TCP窗口与MSS

6.1 窗口缩放

从 SYN 抓包可以看到,客户端和服务端都协商了窗口缩放因子:

ini 复制代码
192.168.0.23.42778 > 192.168.0.201.80: Flags [S],
    seq 619644684, win 64240, options [mss 1460,sackOK,TS val 18321871 ecr 0,nop,wscale 7]
参数 说明
win 64240 SYN 包中的通告窗口
wscale 7 窗口缩放因子
实际窗口 64240 × 2^7 = 8,222,720 字节 约 7.8 MB
mss 1460 最大段大小

没有窗口缩放选项时,TCP 窗口最大只有 65535 字节(16 bit),在高带宽延迟网络中会成为瓶颈。窗口缩放选项将窗口扩展为 32 bit,最大可达 1GB。

6.2 MSS协商

MSS(Maximum Segment Size)在 SYN 阶段协商,双方各通告自己的 MSS。本例中双方 MSS 均为 1460 字节,这是以太网 MTU 1500 减去 IP 头 20 字节和 TCP 头 20 字节后的标准值。

6.3 内核TCP统计

通过 /proc/net/tcpnstat 可以查看内核 TCP 统计:

bash 复制代码
nstat -az | grep -i 'Tcp*' | head -15

真实输出:

复制代码
TcpActiveOpens                  226                0.0
TcpPassiveOpens                 117                0.0
TcpAttemptFails                 9                  0.0
TcpEstabResets                  72                 0.0
TcpInSegs                       14259              0.0
TcpOutSegs                      11275              0.0
TcpRetransSegs                  56                 0.0
TcpInErrs                       0                  0.0
TcpOutRsts                      994                0.0
TcpInCsumErrors                 0                  0.0
TcpExtSyncookiesSent            0                  0.0
TcpExtSyncookiesRecv            0                  0.0
TcpExtSyncookiesFailed          0                  0.0
TcpExtEmbryonicRsts             5                  0.0
统计项 含义
TcpActiveOpens 226 主动发起的连接数(客户端角色)
TcpPassiveOpens 117 被动接受的连接数(服务端角色)
TcpInSegs 14259 接收的 TCP 段总数
TcpOutSegs 11275 发送的 TCP 段总数
TcpRetransSegs 56 重传的 TCP 段数
TcpOutRsts 994 发送的 RST 段数

七、TCP保活机制

TCP Keepalive 用于检测长时间空闲的连接是否仍然存活。通过 sysctl 查看内核保活参数:

bash 复制代码
sysctl net.ipv4.tcp_keepalive_time net.ipv4.tcp_keepalive_intvl \
       net.ipv4.tcp_keepalive_probes net.ipv4.tcp_fin_timeout \
       net.ipv4.tcp_max_syn_backlog net.ipv4.tcp_tw_reuse

真实输出:

ini 复制代码
net.ipv4.tcp_keepalive_time = 7200
net.ipv4.tcp_keepalive_intvl = 75
net.ipv4.tcp_keepalive_probes = 9
net.ipv4.tcp_fin_timeout = 60
net.ipv4.tcp_max_syn_backlog = 1024
net.ipv4.tcp_tw_reuse = 2
参数 默认值 说明
tcp_keepalive_time 7200s (2小时) 连接空闲多久后开始发送保活探测
tcp_keepalive_intvl 75s 两次探测之间的间隔
tcp_keepalive_probes 9 探测失败多少次后判定连接死亡
tcp_fin_timeout 60s FIN_WAIT_2 状态保持时间
tcp_max_syn_backlog 1024 SYN 队列最大长度
tcp_tw_reuse 2 TIME_WAIT 端口复用(2=仅环回口)

保活总超时 = 7200 + 75 × 9 = 7875 秒(约 2 小时 11 分钟)。在生产环境中,对于长连接服务(如数据库连接池),通常需要调小 tcp_keepalive_time 以更快发现死连接。

八、TCP拥塞控制

8.1 拥塞控制算法

bash 复制代码
sysctl net.ipv4.tcp_congestion_control net.ipv4.tcp_available_congestion_control \
       net.core.somaxconn net.ipv4.tcp_max_syn_backlog

真实输出:

ini 复制代码
net.ipv4.tcp_congestion_control = cubic
net.ipv4.tcp_available_congestion_control = reno cubic
net.core.somaxconn = 4096
net.ipv4.tcp_max_syn_backlog = 1024

当前系统使用 cubic 算法(Linux 2.6.19 起默认),可用的算法包括 reno 和 cubic。TCP Fast Open 已启用(值为 1)。

8.2 连接内部状态

通过 ss -ti 可以查看已建立连接的拥塞控制内部参数:

perl 复制代码
ESTAB 0  0  192.168.0.23:46292  100.125.12.110:33554
  cubic wscale:7,7 rto:205 rtt:4.967/0.958 ato:40 mss:1452 pmtu:1500
  rcvmss:1460 advmss:1460 cwnd:10 bytes_sent:5814 bytes_acked:5815
  bytes_received:8301 segs_out:58 segs_in:31 data_segs_out:27
  data_segs_in:29 send 23.4Mbps lastsnd:34325 lastrcv:34319
  lastack:34319 pacing_rate 46.8Mbps delivery_rate 2.88Mbps
  delivered:28 app_limited busy:131ms rcv_space:14600
  rcv_ssthresh:66660 minrtt:4.028 snd_wnd:50688
参数 含义
cubic - 拥塞控制算法
wscale 7,7 发送/接收窗口缩放因子
rto 205ms 重传超时时间
rtt 4.967ms 往返时间
rttvar 0.958ms RTT 方差
cwnd 10 拥塞窗口(以段为单位)
mss 1452 实际最大段大小
send 23.4Mbps 当前发送速率
pacing_rate 46.8Mbps 发送节奏速率
snd_wnd 50688 发送窗口大小

九、TCP重传机制

9.1 重传统计

bash 复制代码
cat /proc/sys/net/ipv4/tcp_retries2
nstat -az | grep -i retrans

真实输出:

复制代码
15

TcpRetransSegs                  56                 0.0
TcpExtTCPLostRetransmit         37                 0.0
TcpExtTCPFastRetrans            0                  0.0
TcpExtTCPSlowStartRetrans       0                  0.0
TcpExtTCPRetransFail            0                  0.0
TcpExtTCPSynRetrans             33                 0.0
统计项 含义
tcp_retries2 15 数据段最大重传次数(超过后放弃连接)
TcpRetransSegs 56 重传段总数
TcpExtTCPLostRetransmit 37 丢失的重传段
TcpExtTCPFastRetrans 0 快速重传次数
TcpExtTCPSynRetrans 33 SYN 重传次数

tcp_retries2 = 15 意味着数据段最多重传 15 次,按指数退避算法,总超时约 924 秒后才放弃连接。SYN 重传次数由 tcp_syn_retries 控制(默认 6 次)。

9.2 并发连接测试

发起 5 个并发 HTTP 请求,观察连接建立情况:

bash 复制代码
for i in $(seq 1 5); do curl -s -o /dev/null http://192.168.0.201/ & done
sleep 2
ss -tn dst 192.168.0.201 | head -10
ss -tn dst 192.168.0.201 | wc -l

输出结果显示,由于请求处理速度极快,在 sleep 2 后所有连接已关闭并进入 TIME-WAIT 状态,ss -tn 仅返回 1 行(表头),说明没有活跃的 ESTABLISHED 连接残留。

十、TCP定时器

通过 ss -to 查看连接的定时器信息:

bash 复制代码
ss -to dst 192.168.0.201 2>/dev/null | head -10
ss -tn state established | head -10

真实输出:

yaml 复制代码
State Recv-Q Send-Q Local Address:Port Peer Address:Port Process
---
Recv-Q Send-Q Local Address:Port     Peer Address:Port Process
0      0       192.168.0.23:46292  100.125.12.110:33554
0      64      192.168.0.23:22     114.116.247.146:55650

与 192.168.0.201 的连接已全部关闭(无定时器信息显示),当前 ESTABLISHED 连接为内网管理通道和 SSH 会话。ss -to-o 选项会显示定时器信息(如 timer:(keepalive,30sec,0)),此处无输出表示连接处于空闲状态,未激活任何定时器。

TCP 内部维护四类核心定时器:

定时器 触发场景 默认参数
重传定时器 发出数据后启动 初始 RTO 由 RTT 动态计算
保活定时器 连接空闲超过 keepalive_time 7200s
TIME_WAIT 定时器 主动关闭方进入 TIME_WAIT 2 × MSL(60s)
持续定时器 对端窗口为 0 时启动 由探测周期决定

十一、总结

本文通过真实抓包数据,完整剖析了 TCP 协议的核心机制:

  1. 三次握手:SYN → SYN-ACK → ACK,双方协商初始序列号和窗口参数
  2. 四次挥手:FIN → ACK → FIN → ACK,全双工连接需要双向独立关闭
  3. 连接状态:11 种状态构成完整状态机,TIME_WAIT 是短连接场景的典型状态
  4. 窗口与MSS:窗口缩放(wscale 7)使窗口可达 8MB,MSS 1460 适配以太网 MTU
  5. 保活机制:默认 2 小时开始探测,9 次失败后判定连接死亡
  6. 拥塞控制:cubic 算法,cwnd=10 段,发送速率 23.4Mbps
  7. 重传机制:tcp_retries2=15,SYN 重传 33 次,数据重传 56 次

理解 TCP 这些底层机制,对于排查网络延迟、连接超时、吞吐量瓶颈等生产问题具有重要指导意义。在实际运维中,建议根据业务场景合理调整 tcp_keepalive_timetcp_tw_reusetcp_max_syn_backlog 等参数,以优化连接管理效率。

相关推荐
甲维斯1 小时前
《钢铁洪流》官网搞定,纯AI制作,Opus5操刀!
前端·人工智能·游戏开发
szephyr1 小时前
WebSocket 实战:心跳、断线重连、鉴权,一次讲清
前端·websocket·node.js·长连接·实时通信
默_笙2 小时前
🚋 从流水线到地铁网:为什么复杂 AI 都要拆成多 Agent(上)——LangGraph 基础入门
前端·javascript
科技苑2 小时前
前后端分离与微服务架构如何协同?
前端·后端·前端框架
神秘的猪头2 小时前
TypeScript 高级用法全解析:从泛型到 infer,把类型系统真正用起来
前端·typescript
moMo2 小时前
React Hooks 与闭包
前端·react.js
柚yuzumi3 小时前
彻底搞懂 JavaScript 类型转换:显式转换、隐式转换与 ToPrimitive
前端·javascript·node.js
向北丶3 小时前
vscode 导入语句排序和删除未使用的导入
前端·visual studio code