深入理解TCP协议(二):TCP可靠传输与高效通信机制详解
本文深入TCP的七大核心工作机制:确认应答、超时重传、滑动窗口、流量控制、拥塞控制、延迟应答与捎带应答。详解丢包的本质与去重、超时时间如何确定、滑动窗口的发送流程与空间复用、快速重传、零窗口探测、窗口扩大因子、慢启动与拥塞避免,以及各机制如何协同实现TCP既可靠又高效的传输,为理解三次握手与四次挥手打下基础。
前言:从"字段"到"机制"
在上一篇文章中,我们认识了 TCP 报头中的各个字段:序号、确认序号、窗口大小、标志位等。但仅仅知道字段含义,并不等于理解了 TCP 的工作方式。例如:
- 客户端发送的数据在网络中丢失了,TCP 是如何发现的?
- 客户端没有收到确认,是否就意味着数据一定没有到达?
- 客户端连续发送多个报文,服务器如何确认收到了哪些数据?
- 发送速度远大于接收方的处理速度,TCP 如何避免接收缓冲区被占满?
- 网络本身发生拥塞,TCP 如何判断并调整发送速度?
这些问题不是某一个字段单独完成的,而是由 TCP 的一系列机制协同完成。本文从 TCP 的工作机制出发,介绍七大核心机制:
| 机制 | 要解决的问题 |
|---|---|
| 确认应答机制 | 确认数据是否被接收 |
| 超时重传机制 | 数据丢失后重新发送 |
| 滑动窗口机制 | 允许连续发送多个数据,提高传输效率 |
| 流量控制机制 | 防止发送速度超过接收方的处理能力 |
| 拥塞控制机制 | 防止发送速度超过网络的承载能力 |
| 延迟应答机制 | 减少 ACK 报文数量,降低网络开销 |
| 捎带应答机制 | 将 ACK 与数据合并发送,提高网络利用率 |
这些机制相互配合,共同构成了 TCP 可靠且高效的通信方式。接下来我们从最基础的确认应答机制开始。
一、确认应答机制
发送端发送一个 TCP 报文,接收端收到后,会给发送端回复一个 ACK 报文(ACK 报文本质也是一个 TCP 报文,只是 ACK 标志位置为 1,告诉对端"我收到了你发送过来的数据",同时表明确认序号有效)。
TCP 将发送缓冲区中的每个字节都进行了编号,即序列号。对于发送出去的 TCP 报文,它的序号为要发送数据的起始序号。当主机 B 收到主机 A 的报文后,返回的 ACK 报文其确认序号 = 接收报文的序号 + 接收报文的数据长度。
再次明确确认序号的定义:接收方已经收到指定序号之前的所有数据,下一次希望接收的数据序号。
确认应答的本质
- 主机 A 收到主机 B 的确认应答,就一定能保证发送的数据已被主机 B 接收;
- 主机 A 没有收到确认应答,则主机 B"可能收到,也可能没收到",为保证可靠性,主机 A 必须通过某种机制重新发送。
一句话总结:单独的确认应答机制不能保证数据最终一定到达,但可以保证确认序号之前的数据一定到达。
二、超时重传机制
发送方没有收到应答时,无法保证报文是否被接收,因此需要重新发送——这就是超时重传机制。
2.1 理解丢包的本质
发送方没收到应答,只存在两种丢包情况:
| 情况 | 说明 | 发送方的处理 |
|---|---|---|
| 数据报文丢失 | 发送方发送的 TCP 报文在网络传输过程中丢失 | 重新发送该报文 |
| ACK 报文丢失 | 接收方已收到报文并回复 ACK,但 ACK 在传输中丢失 | 重新发送该报文(此时涉及去重) |
对于第二种情况,接收方不会重复接收已收到的报文,它是通过确认序号来判断的:接收方会维护已收到的确认序号,当发送方重传的报文序号落在已确认范围内时,直接丢弃,从而完成去重。
2.2 超时时间如何确定
网络环境不断变化,报文往返时间(RTT)并不固定,因此超时时间不能设成固定值:
- 设置太长:报文丢失后要等待很久才重传,降低效率;
- 设置太短:正常传输时间稍长就被误判为丢失,造成不必要的重传。
因此 TCP 根据测量得到的 RTT 动态计算合理的超时时间。发生超时重传后,TCP 还会逐渐增加后续的等待时间(第一次超时等待、第二次等待更久、第三次继续延长……),避免网络异常或拥塞时频繁重传、进一步占用网络资源。但当重传累计到一定次数后,TCP 会认为网络或对端主机出现异常,不再重传,而是强制关闭连接。具体的超时计算与重传次数限制由 TCP 实现决定。
三、滑动窗口机制
如果每发送一个报文都必须等 ACK 到达才能发送下一个,通信效率会非常低——发送方会消耗大量时间等待 ACK,尤其是在高延迟网络下,即使双方处理能力很强也无法充分利用带宽。
TCP 的答案是:允许发送方在没有收到 ACK 的情况下连续发送多个报文,通过后续收到的 ACK 确认哪些数据已被接收。管理这部分"已发送但未确认数据"的机制,就是滑动窗口机制。
3.1 深入理解 send/write 与发送缓冲区
每个 TCP Socket 都有一个发送缓冲区。send/write 的本质是把应用层数据拷贝到内核级发送缓冲区,而不是直接发送到网络。发送缓冲区中的数据什么时候发、一次发多少、丢失怎么办,全由操作系统基于 TCP 协议决定。
可以把发送缓冲区想象成 char buffer[N],划分为三个部分:
| 区域 | 状态 |
|---|---|
| 已发送且已确认 | 数据使命完成,缓冲区空间可被释放复用 |
| 已发送但未确认(滑动窗口区域) | 等待 ACK,窗口左边界所在 |
| 尚未发送 | 等待进入窗口的数据 |
3.2 理解滑动窗口
窗口可以理解为一端连续的子数组:发送方要发送 1~10 的数据,假设发送窗口大小为 4,则一次最多发送 1、2、3、4,即发送的数据量不超过窗口大小。
在 TCP 中,滑动窗口的本质就是发送缓冲区中"可以直接发送、暂时不需要确认"的一部分数据。之所以叫"滑动",是因为随着发送方不断收到 ACK,已发送未确认的数据逐渐变成已发送已确认,窗口左边界不断右移,右边界也随之右移,整个窗口不断向前滑动。
滑动窗口的意义:允许 TCP 在等待 ACK 的过程中继续发送一定范围内的数据,减少等待时间,提高网络通信效率。
3.3 滑动窗口下的发送流程与丢包处理
假设窗口最大为 4000 字节,发送方可以一次性发送 11000、10012000、20013000、30014000 四段数据而无需等待 ACK;收到第一个 ACK 后窗口右滑,继续发送 4001~5000,依此类推。
如果数据丢失怎么办?滑动窗口不会跳过未收到的报文,丢包可分为三类:
(1)最左侧丢失
- 数据报文丢失:主机 B 收到窗口内其他报文后,仍会发送 ACK 确认序号为丢失报文序号(如 1 号),告诉主机 A"1 号之前我已收到,请从 1 号重新发送"。此时窗口左边界不改变,必须收到最左侧报文的确认才能右滑。主机 B 则根据确认序号判断其他报文是否已收到:已收到的丢弃,未收到的先缓存、不交付上层,待最左侧报文到达后按序交付。
- ACK 丢失:主机 B 对收到的所有报文都做应答,确认序号不断增大;主机 A 虽然没收到最左侧的应答,但收到后续报文的应答后,通过确认序号也能推断最左侧报文已被接收,从而将发送序号更新为最大的确认序号,窗口左边界右滑,继续发送新数据。
(2)中间丢失、(3)最右侧丢失:都可以转化为最左侧丢失的情况——中间报文丢失时,窗口左边界会移动到丢失报文的位置;最右侧丢失同理。
3.4 发送缓冲区的空间复用
随着报文不断发送,已发送且已确认的数据不再需要,对应缓冲区空间可以被释放并重新利用,存放后续要发送的新数据。因此可以把发送缓冲区抽象成环形队列来理解:滑动窗口只是其中当前允许发送的一部分区域。
3.5 快速重传机制
滑动窗口带来一个新问题:如果某个报文丢失,接收方返回的 ACK 确认序号只能是最小的那个丢失报文,窗口左边界无法滑动,只能等超时重传,可靠性没问题但效率有损失。
因此 TCP 引入快速重传机制:当接收方收到丢失数据后面的数据时,会不断发送重复 ACK(如 ACK=1001)。发送方连续收到多个相同 ACK,就能推断"接收方一直在等某个数据,后面的数据已到达",从而不等超时,直接重传可能丢失的数据。
注意:快速重传是超时重传的"锦上添花"而非"雪中送炭"。它无法覆盖所有丢包场景——例如数据丢失后后续没有新数据到达,接收方无法产生足够的重复 ACK,此时仍要依靠超时重传。超时重传机制是可靠传输不可或缺的基础,快速重传是在此基础上的性能优化。
3.6 滑动窗口的大小如何确定
窗口太小,连续发送的数据少,仍需频繁等待 ACK,带宽利用不充分;窗口太大,接收方来不及处理,数据堆积甚至缓冲区溢出,造成丢弃与重传,浪费网络资源。
滑动窗口的大小取决于对方的接收能力:滑动窗口大小 = 16 位窗口大小字段(rwnd,接收窗口)。但 TCP 不仅考虑双方通信,还考虑网络环境:
- rwnd(接收窗口):接收方将接收缓冲区剩余空间告知发送方,用于流量控制;
- cwnd(拥塞窗口):发送方根据网络拥塞情况自行调整,用于拥塞控制。
最终:滑动窗口的大小 = min(rwnd, cwnd),实际发送量不能超过这两个窗口允许的范围。
四、流量控制机制
流量控制要解决的问题是:发送方发送速度太快,接收方处理不过来怎么办?
16 位窗口大小字段表示接收方接收缓冲区剩余空间的大小,接收方通过报文持续通告自己的接收能力,发送方据此控制发送量。双方接收能力的交互在三次握手时已经明确,后续通过 ACK 报文动态调整窗口大小。
4.1 零窗口探测
如果接收方缓冲区满了,会把窗口大小设置为 0,发送方收到后将滑动窗口置 0、不再发送数据。那么当接收方处理完数据、缓冲区重新出现空闲时,发送方怎么知道?
发送方发现通告窗口为 0 时并不会永远等待,而是启动一个持续计时器,经过一段时间后主动发送一个不携带数据的 TCP 报文,询问接收方当前窗口大小,接收方通过 ACK 告知。这就是零窗口探测——它的目的不是传输数据,而是避免发送方因零窗口通告而永久陷入等待。
4.2 16 位窗口大小的限制与窗口扩大因子
窗口字段只有 16 位,最大表示 65535 字节,那 TCP 的窗口最大就只有 65535 字节吗?并不是。TCP 报头的选项字段中存在窗口扩大因子(Window Scale):
实际窗口大小 = 窗口字段值 × 2^M(M 为窗口扩大因子)
例如:窗口字段 = 10000,M = 2,实际窗口大小 = 10000 × 4 = 40000 字节。
窗口扩大因子在连接建立过程中协商,连接建立后双方按协商结果解释窗口大小。因此 16 位窗口字段并不代表窗口上限只有 65535 字节。
五、拥塞控制机制
流量控制只解决了接收方的处理能力问题。假设接收方缓冲区足够大、应用处理足够快,发送方就能无限提速吗?答案是否定的——因为发送方和接收方之间还隔着网络本身的承载能力。
少量发送方一般不会轻易造成严重拥塞,但当大量主机同时通信时,共享的带宽和网络设备处理能力就可能成为瓶颈(就像旅游景点人多了网速变慢)。如果网络拥塞时发送方仍按原速发送,甚至在丢包后大量重传,会进一步加重网络压力,使数据包不断堆积,通信效率进一步下降。
因此 TCP 必须根据网络当前状况动态调整发送速度,这就是拥塞控制机制。
5.1 拥塞窗口
TCP 用**拥塞窗口(Congestion Window,cwnd)**来考虑"当前网络能够承受多少数据",与考虑"接收方能接收多少数据"的 rwnd 共同限制发送量:
| 窗口 | 关注对象 | 作用 |
|---|---|---|
| rwnd(接收窗口) | 接收方 | 流量控制:接收方还能接收多少数据 |
| cwnd(拥塞窗口) | 网络 | 拥塞控制:网络能承受多少数据 |
实际发送滑动窗口 = min(rwnd, cwnd)。例如 rwnd=10000、cwnd=4000,虽然接收方能接收 10000 字节,但网络只能承受 4000 字节,发送方最多只能发 4000 字节;反之 rwnd=4000、cwnd=10000 时,限制发送速度的则是接收方。
简单理解:流量控制关注接收方,拥塞控制关注网络。
5.2 TCP 如何判断网络是否拥塞
TCP 无法直接看到网络内部状态,而是通过传输过程中表现出来的现象判断,最典型的就是数据包丢失。
- 发生丢包 → TCP 认为当前网络可能已无法承受之前的发送速度 → 减小 cwnd、降低发送速度;
- 持续发送且数据都得到确认 → 认为网络状况良好 → 逐渐增大 cwnd、尝试提高发送速度。
于是 TCP 形成不断"试探网络承载能力"的循环:网络良好 → 增大 cwnd → 提高速度 → 继续观察 → 大量丢包 → 减小 cwnd → 降低速度 → 重新尝试增大 cwnd。这就是 TCP 拥塞控制的核心思想。
5.3 慢启动
刚开始发送数据时,如果 cwnd 一开始就设得很大并立即冲击网络,很可能一开始就造成拥塞。因此 TCP 从较小的拥塞窗口开始,逐渐增加——这就是慢启动(Slow Start)。
这里的"慢"不是指传输速度慢,而是指不会一开始就以很大的发送量冲击网络,而是逐渐增加发送量,探索网络能承受的范围。慢启动阶段 cwnd 快速增长:1 → 2 → 4 → 8 → 16 → 32,整体呈指数增长趋势。
5.4 拥塞避免
如果一直指数增长(1 → 2 → 4 → 8 → 16 → 32 → 64 → 128…),cwnd 很快就会超过网络承载能力。因此当 cwnd 增长到一定程度后,TCP 进入**拥塞避免(Congestion Avoidance)**阶段,更谨慎地增加 cwnd:
| 阶段 | 增长方式 | 特点 |
|---|---|---|
| 慢启动 | 1 → 2 → 4 → 8 → 16 → 32 | 快速探索网络承载能力 |
| 拥塞避免 | 32 → 33 → 34 → 35 → 36 | 接近网络承载能力后谨慎提速 |
5.5 发生网络拥塞后怎么办
TCP 在增大 cwnd 的过程中发现丢包,说明发送速度可能超过了网络承载能力,此时需要降低 cwnd。TCP 有两种发现丢包的方式:
- 超时重传:长时间没收到 ACK 最终超时,说明网络问题比较严重,TCP 大幅降低发送速度并重新进入慢启动;
- 快速重传:通过重复 ACK 提前发现丢包,无需等待超时,拥塞窗口的调整相对温和。
不需要深入具体的窗口调整公式,只需要理解:发生丢包 → TCP 判断网络可能拥塞 → 减小拥塞窗口 → 降低发送速度。网络恢复后,TCP 又重新增大 cwnd,继续探测网络承载能力,如此循环往复。
六、延迟应答机制
接收方每收到一个数据报文就立即返回 ACK,网络中会产生大量 ACK 报文,增加额外开销。因此 TCP 采用延迟应答:收到数据后不立即发送 ACK,而是暂时等待一小段时间;等待期间如果又收到新数据,就可以用一个 ACK 对多个数据进行确认,从而减少 ACK 报文数量。
延迟应答对流量控制还有额外帮助:等待期间应用层可能读走了接收缓冲区中的数据,缓冲区出现更多空闲空间,接收方再发 ACK 时就可以通告更新后的、更大的窗口大小,告诉发送方"我现在还能接收更多数据"。不过需要注意:延迟应答的核心目的仍然是减少 ACK 数量,而不是专门为了扩大窗口。
七、捎带应答机制
捎带应答的核心思想:在发送 ACK 的同时,如果本机恰好也有数据要发送,就把 ACK 和数据放在同一个 TCP 报文中发送,从而减少单独 ACK 报文带来的网络开销。
例如服务器在回答客户端问题(发送自己的数据)的同时,也携带了对客户端数据的确认编号——一个报文同时完成"发送自己的数据"和"确认收到的数据"。在上一篇文章中我们已结合序号和确认序号详细介绍过该机制,这里作为提高通信效率的机制补充,保证本章内容的完整性。
八、总结
TCP 并不是依靠某一个单独机制实现可靠通信,而是多个机制相互配合:
| 机制 | 作用 |
|---|---|
| 确认应答机制 | 确认数据是否接收 |
| 超时重传机制 | 数据丢失后重新发送 |
| 滑动窗口机制 | 允许连续发送多个数据,提高传输效率 |
| 快速重传机制 | 尽快发现丢失的数据 |
| 流量控制机制 | 防止发送速度超过接收方处理能力 |
| 拥塞控制机制 | 防止发送速度超过网络承载能力 |
| 延迟应答 + 捎带应答 | 进一步减少网络通信开销 |
通过这些机制,TCP 在可靠性、传输效率、接收方处理能力、网络承载能力之间进行协调,使数据能够可靠、高效地在网络中传输。
到目前为止,我们讨论的都是"TCP 建立连接之后如何进行数据传输"。新的问题随之而来:TCP 的连接究竟是如何建立的?为什么建立连接需要三次握手?为什么关闭连接需要四次挥手?为什么关闭后还要经历 TIME_WAIT?连接建立与关闭过程中双方状态如何变化?
这些问题属于 TCP 的连接管理机制。下一篇文章,我们将从 TCP 的连接管理入手,详细介绍三次握手、四次挥手、TCP 状态转换以及连接建立与关闭过程中的相关机制。
SEO 关键词
TCP可靠传输、TCP三次握手、TCP四次挥手、超时重传、滑动窗口、快速重传、流量控制、拥塞控制、慢启动、拥塞避免、延迟应答、捎带应答、TCP机制、网络协议、工业互联网
