📚 网络安全Kali学习系列 · 模块0-2 计算机网络
07 计算机网络基础:OSI 七层模型与 TCP/IP 协议栈
08 TCP/UDP 协议详解:三次握手与四次挥手(当前篇)
09 HTTP/HTTPS 协议与 TLS
10 DNS 解析与 ARP 协议

TCP/UDP 协议详解:三次握手与四次挥手

难度:基础 | 上一篇你了解了 OSI 七层模型,本篇深入传输层的两大核心协议
读完本篇你将能:用 ss 命令观察 TCP 连接的 11 种状态,理解三次握手和四次挥手每一步的序列号变化规律,区分 TCP 和 UDP 的适用场景,并用 tcpdump 实际抓取握手报文进行分析。
📑 本文目录
01TCP 协议概览:报文首部与标志位
02三次握手:建立可靠连接
03四次挥手:优雅断开连接
04TCP 状态机:11 种状态全解
05UDP 协议:轻量快速传输
06实战抓包:tcpdump 与 nmap 扫描

01 TCP 协议概览:报文首部与标志位

上一篇你已掌握了 OSI 七层模型和 TCP/IP 协议栈的整体架构。现在进入传输层,深入 TCP 协议的内部机制。TCP(Transmission Control Protocol,传输控制协议)是面向连接的、可靠的、基于字节流的传输层协议,定义在 RFC 793 中。

TCP 的可靠性建立在两个核心机制上:序列号确认机制(保证数据有序、不丢)和重传机制(丢失自动重发)。要理解这些机制,首先需要认识 TCP 报文首部的关键字段。

TCP 报文首部关键字段

TCP 报文首部长度固定为 20 字节(不含选项),以下是与握手挥手直接相关的字段:

字段 长度 作用
源端口 2 字节 发送方端口号
目的端口 2 字节 接收方端口号
序列号 (seq) 4 字节 本报文数据段第一个字节的编号
确认号 (ack) 4 字节 期望收到对方下一个报文的序列号
标志位 (Flags) 6 bit 控制连接建立、断开和数据传输

六个标志位

标志位是 TCP 报文的"指挥信号",共 6 个比特,每个比特控制一种行为:

SYN 同步位,请求建立连接。SYN=1 时 seq 字段携带初始序列号
ACK 确认位,确认号字段有效。连接建立后 ACK 恒为 1
FIN 终止位,请求断开连接。发送方没有更多数据要发送了
RST 重置位,立即断开连接,不经过正常挥手流程。异常关闭时使用
PSH 推送位,要求接收方立即将数据交给应用层,不等缓冲区填满
URG 紧急位,报文中包含紧急数据,紧急指针字段有效
💡 小贴士
序列号不是从 0 开始的,而是操作系统随机生成一个 32 位初始序列号(ISN)。这是为了防止 seq 被预测后进行 TCP 会话劫持攻击。Linux 内核使用基于时钟的 ISN 生成算法,每 4 微秒变化一次。

02 三次握手:建立可靠连接

你已经认识了 TCP 报文的关键字段和标志位。现在来看这些字段如何配合完成连接建立。TCP 在传输数据前必须先建立连接——这个过程就是三次握手(Three-way Handshake)。

想象两支军事小队要通过电台建立加密通信。A 队发信:"请求建立通信频道"(SYN)。B 队回复:"收到请求,同意建立,请确认你也能收到我的信号"(SYN+ACK)。A 队最后回应:"确认收到你的信号,频道建立完毕"(ACK)。三次交互确保双方的发送和接收能力都经过验证。

握手流程详解

TCP 三次握手过程
客户端
SYN seq=x
服务器
↓
客户端
SYN+ACK seq=y, ack=x+1
服务器
↓
客户端
ACK ack=y+1
服务器
连接建立完成,开始传输数据

逐步拆解每一步的序列号变化:

1 第一次握手:客户端发送 SYN 报文,标志位 SYN=1,随机生成初始序列号 seq=x。客户端进入 SYN_SENT 状态。这一步的意义是:"我想和你建立连接,我的初始序列号是 x。"
2 第二次握手:服务器收到 SYN 后,回复 SYN+ACK 报文。SYN=1 且 ACK=1,自己生成初始序列号 seq=y,确认号 ack=x+1(表示"收到了你的 seq=x,期望你下次发 seq=x+1")。服务器进入 SYN_RCVD 状态。这一步同时完成了两件事:确认收到了客户端的连接请求,并发出自己的连接请求。
3 第三次握手:客户端收到 SYN+ACK 后,发送 ACK 报文。ACK=1,ack=y+1(确认收到服务器的 seq=y)。此时 seq=x+1。双方进入 ESTABLISHED 状态,连接建立完毕。

为什么是三次而不是两次

这是面试和考试的高频问题。核心原因是:三次握手能确保双方的收发能力都经过验证。

假设只有两次握手:客户端发 SYN,服务器回 SYN+ACK 后直接进入 ESTABLISHED。如果客户端的 SYN 因为网络延迟滞留,服务器会为一个早已失效的请求保持连接,浪费资源。三次握手下,服务器发的 SYN+ACK 需要等客户端的 ACK 确认才真正建立连接——如果客户端没有收到 SYN+ACK,不会发 ACK,服务器超时后自动断开。

用 ss 命令观察连接状态

在 Kali 终端中,用 ss 命令可以实时观察 TCP 连接状态:

Shell
1
2
3
4
5
6
ss -tn
# 输出示例:
# State Recv-Q Send-Q Local Address:Port Peer Address:Port
# ESTAB 0 0 192.168.1.100:45678 93.184.216.34:443
# TIME-WAIT 0 0 192.168.1.100:38912 142.250.80.46:80
# SYN-SENT 0 0 192.168.1.100:52301 10.0.0.1:22

可以看到每条连接的 State 列直接显示当前 TCP 状态。-t 表示只看 TCP,-n 表示不解析域名(显示 IP 而非主机名,速度更快)。

过滤特定状态的连接:

Shell
ss -tn state syn-sent
# 只显示正在发起连接(SYN已发,等待响应)的TCP连接
# State Recv-Q Send-Q Local Address:Port Peer Address:Port
# SYN-SENT 0 0 192.168.1.100:52301 10.0.0.1:22

03 四次挥手:优雅断开连接

你已理解了连接如何建立。现在看连接如何断开。TCP 是全双工通信——双方都能同时发送和接收数据。因此断开连接时,每个方向的关闭需要独立完成,总共需要四次交互,称为四次挥手(Four-way Handshake)。

挥手流程详解

TCP 四次挥手过程
客户端
FIN seq=u
服务器
↓
客户端
ACK ack=u+1
服务器
(服务器可能继续发送剩余数据)
↓
客户端
FIN seq=v
服务器
↓
客户端
ACK ack=v+1
服务器
客户端等待 2MSL 后关闭,服务器收到 ACK 后关闭

逐步拆解:

1 第一次挥手:客户端发送 FIN 报文,FIN=1,seq=u。客户端进入 FIN_WAIT_1 状态。表示:"我没有数据要发送了。"
2 第二次挥手:服务器收到 FIN,回复 ACK=1,ack=u+1。服务器进入 CLOSE_WAIT 状态。此时进入半关闭状态——客户端不再发数据,但服务器仍可以发送未完成的数据。客户端收到 ACK 后进入 FIN_WAIT_2 状态。
3 第三次挥手:服务器数据发完后,发送 FIN=1,seq=v。服务器进入 LAST_ACK 状态。表示:"我的数据也发完了。"
4 第四次挥手:客户端收到 FIN,回复 ACK=1,ack=v+1。客户端进入 TIME_WAIT 状态,等待 2MSL(最大报文生存时间)后关闭。服务器收到 ACK 后进入 CLOSED 状态。

为什么挥手需要四次

握手时第二次 SYN+ACK 可以合并为一个报文,因为服务器收到 SYN 时还没有数据要发。但挥手时,服务器收到客户端的 FIN 后,可能还有未发完的数据——所以先回 ACK("收到你的关闭请求"),等数据发完后才发自己的 FIN。如果服务器没有残留数据,第二次和第三次挥手可以合并,变成三次挥手。

TIME_WAIT 状态详解

TIME_WAIT 是四次挥手后客户端进入的最后一个状态,持续 2MSL(Linux 默认 60 秒,MSL=30 秒)。这个等待有两个目的:

① 确保最后一个 ACK 能到达服务器。如果 ACK 丢失,服务器会超时重发 FIN,客户端还能在 TIME_WAIT 期间收到并重发 ACK。
② 让本次连接的所有报文在网络中消亡。2MSL 足够让任何滞留的 TCP 报文过期,防止它们干扰下一个使用相同四元组(源IP:源端口 → 目的IP:目的端口)的新连接。
💡 小贴士
高并发服务器上大量 TIME_WAIT 会占用端口资源。查看当前 TIME_WAIT 数量:ss -s 输出的 timers 行。如需优化,可调整 net.ipv4.tcp_tw_reuse 参数允许复用 TIME_WAIT 端口。

04 TCP 状态机:11 种状态全解

前面你通过握手和挥手了解了部分 TCP 状态。现在把所有 11 种状态串成完整的状态机,这是分析网络异常和安全排查的基础。

完整状态列表

状态 所属角色 含义
CLOSED 双方 初始状态,无连接
LISTEN 服务器 等待连接请求
SYN_SENT 客户端 已发 SYN,等待服务器回应
SYN_RCVD 服务器 收到 SYN,已回 SYN+ACK
ESTABLISHED 双方 连接建立,可以传输数据
FIN_WAIT_1 主动关闭方 已发 FIN,等待对方 ACK
FIN_WAIT_2 主动关闭方 收到 ACK,半关闭,等对方 FIN
CLOSE_WAIT 被动关闭方 收到对方 FIN,已回 ACK,等自己发 FIN
CLOSING 主动关闭方 双方同时发 FIN 的罕见状态
LAST_ACK 被动关闭方 已发 FIN,等待最后 ACK
TIME_WAIT 主动关闭方 等待 2MSL 确保报文消亡

安全视角:用状态检测异常

从安全角度,异常的 TCP 状态分布可能暗示攻击行为:

SYN_FLOOD 大量 SYN_RCVD 状态连接——攻击者发大量 SYN 但不完成第三次握手,耗尽服务器半连接队列
端口扫描 大量 SYN_SENT 连向不同端口——nmap 等扫描工具的特征
后门连接 意外的 ESTABLISHED 连接——非工作时间的外部 IP 长连接可能是后门
资源泄露 大量 CLOSE_WAIT——应用代码未正确关闭 socket,连接卡在半关闭状态

统计各状态连接数量:

Shell
1
2
3
4
5
6
7
8
ss -tan | awk 'NR>1{print $1}' | sort | uniq -c | sort -rn
# 输出示例:
# 142 TIME-WAIT
# 38 ESTAB
# 12 CLOSE-WAIT
# 5 SYN-RECV
# 0 LISTEN
# 如果SYN-RECV突然飙升到上千,很可能遭受SYN Flood攻击

05 UDP 协议:轻量快速传输

你已深入理解了 TCP 的连接管理机制。现在来看传输层的另一个协议——UDP。UDP(User Datagram Protocol,用户数据报协议)是无连接的、不可靠的、面向报文的传输层协议,定义在 RFC 768 中。

如果说 TCP 像挂号信——每一步都有签收确认,丢了就重寄;UDP 就像广播喊话——发出去就不管了,收没收到是你的事。这种"不管"换来了极低的延迟和开销。

UDP 报文结构

UDP 首部只有 8 字节,相比 TCP 的 20 字节极为精简:

字段 长度 作用
源端口 2 字节 发送方端口号(可省略,填 0)
目的端口 2 字节 接收方端口号
长度 2 字节 UDP 报文总长度(首部+数据)
校验和 2 字节 检测数据完整性(可选填 0 表示不校验)

没有序列号、没有确认号、没有标志位——UDP 不维护连接状态,发完就忘。

TCP vs UDP 对比

特性 TCP UDP
连接方式 面向连接(三次握手) 无连接
可靠性 可靠(确认、重传、排序) 不可靠(尽最大努力交付)
首部开销 20 字节 8 字节
传输方式 字节流 数据报
拥塞控制 有(慢启动、拥塞避免) 无
适用场景 网页、邮件、文件传输 视频直播、DNS、游戏

常见 UDP 应用

DNS 域名解析请求报文小且要求快速响应,UDP 完美匹配(端口 53)
DHCP 设备获取 IP 时还没有完整网络配置,无法先建 TCP 连接(端口 67/68)
流媒体 视频通话丢几帧不影响整体体验,重传反而增加延迟
TFTP 简单文件传输协议,用于无盘启动等场景(端口 69)
⚠️ 常见错误
"UDP 完全没有校验,数据可能损坏" — 这是常见误解
✓ 正确:UDP 首部有 2 字节校验和字段,会校验数据和伪首部。只是校验出错误后直接丢弃,不重传。校验和填 0 才表示跳过校验。

06 实战抓包:tcpdump 与 nmap 扫描

理论理解之后,用工具实际观察 TCP 报文。tcpdump 是 Linux 下最强大的命令行抓包工具,能让你亲眼看到三次握手的每一个报文。

tcpdump 抓取三次握手

在 Kali 终端执行以下命令,然后在另一个终端用 curl 访问任意网站,即可抓到完整的握手过程:

Shell
1
2
3
4
5
6
7
8
9
10
# 终端1:监听eth0网卡的TCP SYN和FIN报文
sudo tcpdump -i eth0 -n 'tcp[tcpflags] & (tcp-syn|tcp-fin) != 0'
# 终端2:发起HTTP连接
curl -s http://example.com > /dev/null
# 终端1输出(三次握手):
# 10:23:01 IP 192.168.1.100.45678 > 93.184.216.34.80: Flags [S], seq 1234567
# 10:23:01 IP 93.184.216.34.80 > 192.168.1.100.45678: Flags [S.], seq 9876543, ack 1234568
# 10:23:01 IP 192.168.1.100.45678 > 93.184.216.34.80: Flags [.], ack 9876544

输出中的 [S] 表示 SYN 标志位,[S.] 表示 SYN+ACK(点号代表 ACK),[.] 表示纯 ACK。注意 seq 和 ack 的对应关系:第一次 seq=1234567,第二次 ack=1234568(=seq+1),与前面讲解完全一致。

nmap SYN 扫描与 TCP 状态

nmap 的 -sS(SYN 扫描,又称半开扫描)直接利用了三次握手的原理:

① 扫描器发 SYN,目标端口如果开放,回 SYN+ACK
② 扫描器收到 SYN+ACK 后不发 ACK 完成握手,而是发 RST 立即断开
③ 目标端口如果关闭,直接回 RST,扫描器据此判断端口状态
Shell
sudo nmap -sS -p 22,80,443 192.168.1.1
# 输出示例:
# PORT STATE SERVICE
# 22/tcp open ssh
# 80/tcp open http
# 443/tcp closed https
💡 小贴士
SYN 扫描被称为"半开扫描"因为它从不完成完整的三次握手。优势是速度快(不等第三次握手)且隐蔽(大多数日志系统只记录完整连接)。这也是为什么服务器上的 SYN_RCVD 状态突增可能是扫描行为——大量 SYN 发出但 ACK 没有回来。
✏️ 动手练习
🟢 基础验证
在 Kali 终端执行 ss -tn,找到一条 ESTABLISHED 状态的连接,记录其四元组(本地IP:端口 → 远程IP:端口)。然后用 ss -tn state established 验证过滤是否生效。
🟡 组合应用
开启两个终端:终端1运行 sudo tcpdump -i eth0 -n 'tcp[tcpflags] & tcp-syn != 0' 抓取 SYN 报文,终端2执行 curl http://example.com。在 tcpdump 输出中找到三次握手的三个报文,写出每一步的 Flags 标记和 seq/ack 值,验证 ack=seq+1 的关系。
🔴 开放挑战
用 ss -tan 统计本机各 TCP 状态的连接数量。如果发现大量 CLOSE_WAIT,查阅资料分析可能的原因(提示:应用程序没有调用哪个系统调用?),并尝试用 strace 或 lsof 定位是哪个进程导致的。
📖 知识回顾
TCP 报文首部 六位标志位 三次握手 四次挥手 TIME_WAIT 11 种 TCP 状态 UDP 报文结构 TCP vs UDP tcpdump 抓包 nmap SYN 扫描
下篇预告
09 HTTP/HTTPS 协议与 TLS
从 TCP 之上的应用层出发,理解 HTTP 请求响应模型、HTTPS 加密握手过程,以及 TLS 如何在 TCP 之上构建安全通道