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

6 阅读10分钟

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 字节(不含选项),其结构如下:

 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 bitURG/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 抓取完整握手过程:

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

抓包结果(真实输出):

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 时序图

  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 的代理连接为例):

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 时序图

  主动关闭方 (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包挥手:

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 连接状态:

ss -tan | awk '{print $1}' | sort | uniq -c | sort -rn

真实输出:

      9 TIME-WAIT
      7 LISTEN
      2 ESTAB
      1 State

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

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

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 状态的连接:

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 抓包可以看到,客户端和服务端都协商了窗口缩放因子:

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]
参数说明
win64240SYN 包中的通告窗口
wscale7窗口缩放因子
实际窗口64240 × 2^7 = 8,222,720 字节约 7.8 MB
mss1460最大段大小

没有窗口缩放选项时,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 统计:

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
统计项含义
TcpActiveOpens226主动发起的连接数(客户端角色)
TcpPassiveOpens117被动接受的连接数(服务端角色)
TcpInSegs14259接收的 TCP 段总数
TcpOutSegs11275发送的 TCP 段总数
TcpRetransSegs56重传的 TCP 段数
TcpOutRsts994发送的 RST 段数

七、TCP保活机制

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

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

真实输出:

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_time7200s (2小时)连接空闲多久后开始发送保活探测
tcp_keepalive_intvl75s两次探测之间的间隔
tcp_keepalive_probes9探测失败多少次后判定连接死亡
tcp_fin_timeout60sFIN_WAIT_2 状态保持时间
tcp_max_syn_backlog1024SYN 队列最大长度
tcp_tw_reuse2TIME_WAIT 端口复用(2=仅环回口)

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

八、TCP拥塞控制

8.1 拥塞控制算法

sysctl net.ipv4.tcp_congestion_control net.ipv4.tcp_available_congestion_control \
       net.core.somaxconn net.ipv4.tcp_max_syn_backlog

真实输出:

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 可以查看已建立连接的拥塞控制内部参数:

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-拥塞控制算法
wscale7,7发送/接收窗口缩放因子
rto205ms重传超时时间
rtt4.967ms往返时间
rttvar0.958msRTT 方差
cwnd10拥塞窗口(以段为单位)
mss1452实际最大段大小
send23.4Mbps当前发送速率
pacing_rate46.8Mbps发送节奏速率
snd_wnd50688发送窗口大小

九、TCP重传机制

9.1 重传统计

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_retries215数据段最大重传次数(超过后放弃连接)
TcpRetransSegs56重传段总数
TcpExtTCPLostRetransmit37丢失的重传段
TcpExtTCPFastRetrans0快速重传次数
TcpExtTCPSynRetrans33SYN 重传次数

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

9.2 并发连接测试

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

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 查看连接的定时器信息:

ss -to dst 192.168.0.201 2>/dev/null | head -10
ss -tn state established | head -10

真实输出:

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_time7200s
TIME_WAIT 定时器主动关闭方进入 TIME_WAIT2 × 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 等参数,以优化连接管理效率。