本文主题内容
- 掌握 TCP 报头格式
- 理解序号、确认应答与超时重传
- 理解三次握手与四次挥手
- 掌握 TCP 状态转换
- 理解滑动窗口与流量控制
- 理解快速重传与拥塞控制
- 理解延迟应答、捎带应答和字节流
- 分析
TIME_WAIT与CLOSE_WAIT
引言:TCP 的复杂性来自传输控制。它不仅要把数据送到对端,还要处理丢包、重复、乱序、接收能力和网络拥塞。理解这些机制,需要把报头字段、状态机和缓冲区联系起来。
一、TCP 基本特点
TCP 全称 Transmission Control Protocol,主要特点:
- 面向连接
- 可靠传输
- 有序字节流
- 全双工
- 提供流量控制
- 提供拥塞控制
TCP 的可靠不是绝对保证应用成功处理,而是保证在连接有效时,把确认过的字节按顺序交付给接收应用;无法继续传输时会通过错误或连接关闭通知应用。
二、TCP 报头

TCP 基本报头为 20 字节,带选项时最多 60 字节。
2.1 端口号
源端口和目的端口用于进程分用。TCP 连接由五元组标识:
text
源IP、源端口、目的IP、目的端口、TCP
2.2 序号与确认号
- 序号:本报文段数据第一个字节在字节流中的编号
- 确认号:期望对方下一次发送的字节序号
确认号具有累计确认语义。例如 ACK 为 5001,表示 5001 之前的字节已经连续收到。
2.3 报头长度
4 位报头长度以 4 字节为单位。值为 5 表示 20 字节,最大值 15 表示 60 字节。
2.4 标志位
常见标志:
SYN:建立连接,同步初始序号ACK:确认号有效FIN:发送方不再发送数据RST:复位连接PSH:提示接收端尽快向应用交付URG:紧急指针有效
2.5 窗口大小
接收端通过窗口字段告诉发送端自己当前还能接收多少数据,用于流量控制。窗口扩大选项可以突破 16 位字段的直接范围。
三、可靠传输
3.1 确认应答
发送端把已发送但未确认的数据保存在发送缓冲区,收到 ACK 后才能释放相应范围。

3.2 序号解决的问题
序号和确认号可以:
- 判断数据是否重复
- 恢复正确顺序
- 表示已经连续收到的范围
- 支撑滑动窗口
接收端发现重复数据时会丢弃重复部分,但仍可能再次发送 ACK,帮助发送端恢复状态。
3.3 超时重传
发送数据后,如果在重传超时时间 RTO 内没有收到有效确认,发送端会重传。

RTO 不能固定得过小或过大:
- 太小:正常延迟也会触发无意义重传
- 太大:真正丢包后的恢复太慢
TCP 根据测量到的往返时间 RTT 及其波动动态估计 RTO,并在连续超时时执行退避。
TCP 可靠性的基础是序号、确认、超时重传和校验,它们共同处理丢失、重复、乱序与损坏。
四、三次握手
4.1 握手过程
- 客户端发送
SYN,携带初始序号x - 服务器发送
SYN + ACK,序号为y,确认号为x + 1 - 客户端发送
ACK,确认号为y + 1
握手完成后双方进入 ESTABLISHED。
4.2 为什么是三次
三次握手使双方确认:
- 自己的发送能力正常
- 自己的接收能力正常
- 对方的发送与接收能力正常
- 双方的初始序号已经同步
两次不足以让服务器确认客户端已经收到自己的初始序号,也更难避免旧连接请求造成状态混乱。
4.3 SYN Flood
服务器收到 SYN 后会保存半连接状态。攻击者大量发送请求但不完成握手,会消耗队列资源。
常见缓解方式包括 SYN Cookies、队列调整、速率限制和上游防护。
五、四次挥手
5.1 关闭过程
TCP 是全双工协议,两个发送方向需要分别关闭:
- 主动关闭方发送
FIN - 被动关闭方发送
ACK - 被动关闭方处理完剩余数据后发送
FIN - 主动关闭方发送
ACK
中间两个报文在条件允许时可能合并,所以抓包中不一定总能看到严格分离的四个报文。
5.2 半关闭
一方发送 FIN 只表示自己不再发送,不妨碍继续接收对方数据。应用可以使用 shutdown(fd, SHUT_WR) 主动关闭写方向。
六、TCP 状态转换


6.1 服务端常见状态
text
CLOSED -> LISTEN -> SYN_RCVD -> ESTABLISHED
ESTABLISHED -> CLOSE_WAIT -> LAST_ACK -> CLOSED
6.2 客户端常见状态
text
CLOSED -> SYN_SENT -> ESTABLISHED
ESTABLISHED -> FIN_WAIT_1 -> FIN_WAIT_2
-> TIME_WAIT -> CLOSED
6.3 TIME_WAIT
主动关闭方在发送最后 ACK 后进入 TIME_WAIT,通常等待 2MSL。
原因包括:
- 最后 ACK 丢失时,可以重新发送
- 让旧连接中的迟到报文在网络中消失,避免影响相同五元组的新连接
服务器大量 TIME_WAIT 不一定是 Bug,它说明服务器主动关闭了许多连接。应先确认协议设计和连接复用方式,再考虑参数调整。
6.4 CLOSE_WAIT
收到对端 FIN 后,本地进入 CLOSE_WAIT。如果长期大量存在,通常表示应用已经知道对端关闭,却没有及时调用 close。
注意:CLOSE_WAIT 通常是应用资源释放问题,不能靠缩短 TCP 内核超时从根本上解决。
七、滑动窗口与流量控制
7.1 滑动窗口
如果每发送一段数据都等待 ACK,吞吐量会受到 RTT 严重限制。滑动窗口允许发送端在未收到确认前连续发送一定范围的数据。

发送缓冲区可以理解为:
text
已经确认 | 已发送未确认 | 允许发送 | 暂时不能发送
收到累计 ACK 后,窗口向右滑动,已经确认的数据可以释放,新的数据进入可发送范围。

7.2 流量控制
接收端通过通告窗口 rwnd 告诉发送端剩余接收空间。发送端不能持续超过接收端处理能力。
当窗口为 0 时,发送端暂停普通数据,并周期性发送窗口探测,防止接收端窗口恢复通知丢失后双方永久等待。
八、快速重传
数据段丢失时,后续乱序数据会让接收端重复确认同一个期望序号。发送端连续收到多个重复 ACK 后,可以不等超时就重传缺失数据。

经典 TCP 通常在收到 3 个重复 ACK 后触发快速重传。现代 TCP 还可以使用 SACK 选择确认,更精确地描述已经收到的非连续区间。
九、拥塞控制
流量控制保护接收端,拥塞控制保护网络。实际发送窗口通常受二者共同限制:
text
发送窗口 = min(rwnd, cwnd)
其中 cwnd 是拥塞窗口。
9.1 慢启动
连接开始时,发送端从较小拥塞窗口起步,每个 RTT 快速增加,用探测方式寻找可用容量。
9.2 拥塞避免
达到慢启动阈值后,窗口增长变得平缓,避免指数增长迅速压垮网络。
9.3 丢包后的处理
- 超时通常被视为较严重拥塞,窗口明显降低
- 重复 ACK 触发快速重传和快速恢复
- 具体算法可能是 Reno、CUBIC、BBR 等
不能把某一张经典曲线当成所有操作系统当前实现的唯一行为,但慢启动、拥塞窗口和反馈调节的思想保持一致。
十、延迟应答与捎带应答
10.1 延迟应答
接收端不一定立即为每个小报文单独发送 ACK,可能短暂等待:
- 看是否有数据可以一起返回
- 看是否能确认更多连续字节
- 给应用读取数据并扩大接收窗口的机会
延迟不能无限持续,否则会降低交互性能。
10.2 捎带应答
如果接收端也要发送业务数据,可以把 ACK 和响应数据放在同一个 TCP 报文段中,减少独立报文数量。
十一、面向字节流
TCP 为每个连接维护发送缓冲区和接收缓冲区。应用写入的是字节,不是独立消息:
- 大块数据可能被拆成多个段
- 多次小写可能被合并
- 接收端每次读取的字节数由当前缓冲区和参数决定
因此所谓粘包并不是 TCP 报文粘在一起,而是应用层没有定义或正确解析消息边界。
十二、TCP 异常情况
12.1 进程终止
进程退出会关闭文件描述符,内核通常仍能发起正常 FIN 关闭。
12.2 主机崩溃或断网
无法发送 FIN 时,对端可能要等到重传超时、保活探测或业务心跳超时后才能发现。
12.3 RST
收到不存在连接的数据、访问未监听端口或连接状态严重不匹配时,可能收到 RST。应用读写会表现为连接重置错误。
12.4 Keepalive 与业务心跳
TCP Keepalive 由内核探测空闲连接,默认参数经常较长。业务心跳可以携带应用状态并使用更适合业务的超时,二者不能完全互相替代。
十三、总结
TCP 通过序号、确认、重传和校验实现可靠有序传输,通过三次握手建立双方状态,通过四次挥手分别关闭两个发送方向。滑动窗口提高吞吐量,接收窗口实现流量控制,拥塞窗口根据网络反馈调整发送强度。
TIME_WAIT 保护连接关闭和旧报文隔离,CLOSE_WAIT 长期堆积通常意味着应用没有关闭连接。TCP 提供字节流,不提供业务消息边界,应用层仍然必须设计协议并正确处理缓冲区。
理解 TCP 的关键,是把报头字段看成状态机和缓冲区的控制信息,而不是孤立地背诵三次握手与四次挥手。