ss 命令观察 TCP 连接的 11 种状态,理解三次握手和四次挥手每一步的序列号变化规律,区分 TCP 和 UDP 的适用场景,并用 tcpdump 实际抓取握手报文进行分析。上一篇你已掌握了 OSI 七层模型和 TCP/IP 协议栈的整体架构。现在进入传输层,深入 TCP 协议的内部机制。TCP(Transmission Control Protocol,传输控制协议)是面向连接的、可靠的、基于字节流的传输层协议,定义在 RFC 793 中。
TCP 的可靠性建立在两个核心机制上:序列号确认机制(保证数据有序、不丢)和重传机制(丢失自动重发)。要理解这些机制,首先需要认识 TCP 报文首部的关键字段。
TCP 报文首部长度固定为 20 字节(不含选项),以下是与握手挥手直接相关的字段:
| 字段 | 长度 | 作用 |
|---|---|---|
| 源端口 | 2 字节 | 发送方端口号 |
| 目的端口 | 2 字节 | 接收方端口号 |
| 序列号 (seq) | 4 字节 | 本报文数据段第一个字节的编号 |
| 确认号 (ack) | 4 字节 | 期望收到对方下一个报文的序列号 |
| 标志位 (Flags) | 6 bit | 控制连接建立、断开和数据传输 |
标志位是 TCP 报文的"指挥信号",共 6 个比特,每个比特控制一种行为:
你已经认识了 TCP 报文的关键字段和标志位。现在来看这些字段如何配合完成连接建立。TCP 在传输数据前必须先建立连接——这个过程就是三次握手(Three-way Handshake)。
想象两支军事小队要通过电台建立加密通信。A 队发信:"请求建立通信频道"(SYN)。B 队回复:"收到请求,同意建立,请确认你也能收到我的信号"(SYN+ACK)。A 队最后回应:"确认收到你的信号,频道建立完毕"(ACK)。三次交互确保双方的发送和接收能力都经过验证。
SYN seq=xSYN+ACK seq=y, ack=x+1ACK ack=y+1逐步拆解每一步的序列号变化:
SYN_SENT 状态。这一步的意义是:"我想和你建立连接,我的初始序列号是 x。"
SYN_RCVD 状态。这一步同时完成了两件事:确认收到了客户端的连接请求,并发出自己的连接请求。
ESTABLISHED 状态,连接建立完毕。
这是面试和考试的高频问题。核心原因是:三次握手能确保双方的收发能力都经过验证。
假设只有两次握手:客户端发 SYN,服务器回 SYN+ACK 后直接进入 ESTABLISHED。如果客户端的 SYN 因为网络延迟滞留,服务器会为一个早已失效的请求保持连接,浪费资源。三次握手下,服务器发的 SYN+ACK 需要等客户端的 ACK 确认才真正建立连接——如果客户端没有收到 SYN+ACK,不会发 ACK,服务器超时后自动断开。
在 Kali 终端中,用 ss 命令可以实时观察 TCP 连接状态:
可以看到每条连接的 State 列直接显示当前 TCP 状态。-t 表示只看 TCP,-n 表示不解析域名(显示 IP 而非主机名,速度更快)。
过滤特定状态的连接:
你已理解了连接如何建立。现在看连接如何断开。TCP 是全双工通信——双方都能同时发送和接收数据。因此断开连接时,每个方向的关闭需要独立完成,总共需要四次交互,称为四次挥手(Four-way Handshake)。
FIN seq=uACK ack=u+1FIN seq=vACK ack=v+1逐步拆解:
FIN_WAIT_1 状态。表示:"我没有数据要发送了。"
CLOSE_WAIT 状态。此时进入半关闭状态——客户端不再发数据,但服务器仍可以发送未完成的数据。客户端收到 ACK 后进入 FIN_WAIT_2 状态。
LAST_ACK 状态。表示:"我的数据也发完了。"
TIME_WAIT 状态,等待 2MSL(最大报文生存时间)后关闭。服务器收到 ACK 后进入 CLOSED 状态。
握手时第二次 SYN+ACK 可以合并为一个报文,因为服务器收到 SYN 时还没有数据要发。但挥手时,服务器收到客户端的 FIN 后,可能还有未发完的数据——所以先回 ACK("收到你的关闭请求"),等数据发完后才发自己的 FIN。如果服务器没有残留数据,第二次和第三次挥手可以合并,变成三次挥手。
TIME_WAIT 是四次挥手后客户端进入的最后一个状态,持续 2MSL(Linux 默认 60 秒,MSL=30 秒)。这个等待有两个目的:
ss -s 输出的 timers 行。如需优化,可调整 net.ipv4.tcp_tw_reuse 参数允许复用 TIME_WAIT 端口。前面你通过握手和挥手了解了部分 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_RCVD 状态连接——攻击者发大量 SYN 但不完成第三次握手,耗尽服务器半连接队列
SYN_SENT 连向不同端口——nmap 等扫描工具的特征
ESTABLISHED 连接——非工作时间的外部 IP 长连接可能是后门
CLOSE_WAIT——应用代码未正确关闭 socket,连接卡在半关闭状态
统计各状态连接数量:
你已深入理解了 TCP 的连接管理机制。现在来看传输层的另一个协议——UDP。UDP(User Datagram Protocol,用户数据报协议)是无连接的、不可靠的、面向报文的传输层协议,定义在 RFC 768 中。
如果说 TCP 像挂号信——每一步都有签收确认,丢了就重寄;UDP 就像广播喊话——发出去就不管了,收没收到是你的事。这种"不管"换来了极低的延迟和开销。
UDP 首部只有 8 字节,相比 TCP 的 20 字节极为精简:
| 字段 | 长度 | 作用 |
|---|---|---|
| 源端口 | 2 字节 | 发送方端口号(可省略,填 0) |
| 目的端口 | 2 字节 | 接收方端口号 |
| 长度 | 2 字节 | UDP 报文总长度(首部+数据) |
| 校验和 | 2 字节 | 检测数据完整性(可选填 0 表示不校验) |
没有序列号、没有确认号、没有标志位——UDP 不维护连接状态,发完就忘。
| 特性 | TCP | UDP |
|---|---|---|
| 连接方式 | 面向连接(三次握手) | 无连接 |
| 可靠性 | 可靠(确认、重传、排序) | 不可靠(尽最大努力交付) |
| 首部开销 | 20 字节 | 8 字节 |
| 传输方式 | 字节流 | 数据报 |
| 拥塞控制 | 有(慢启动、拥塞避免) | 无 |
| 适用场景 | 网页、邮件、文件传输 | 视频直播、DNS、游戏 |
理论理解之后,用工具实际观察 TCP 报文。tcpdump 是 Linux 下最强大的命令行抓包工具,能让你亲眼看到三次握手的每一个报文。
在 Kali 终端执行以下命令,然后在另一个终端用 curl 访问任意网站,即可抓到完整的握手过程:
输出中的 [S] 表示 SYN 标志位,[S.] 表示 SYN+ACK(点号代表 ACK),[.] 表示纯 ACK。注意 seq 和 ack 的对应关系:第一次 seq=1234567,第二次 ack=1234568(=seq+1),与前面讲解完全一致。
nmap 的 -sS(SYN 扫描,又称半开扫描)直接利用了三次握手的原理:
ss -tn,找到一条 ESTABLISHED 状态的连接,记录其四元组(本地IP:端口 → 远程IP:端口)。然后用 ss -tn state established 验证过滤是否生效。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 定位是哪个进程导致的。