☰
中篇:TCP 为什么可靠又高效?滑动窗口、流量控制、拥塞控制与粘包全解析
2026/9/27 3:19:40 网站建设 项目流程

中篇:TCP 为什么可靠又高效?滑动窗口、流量控制、拥塞控制与粘包全解析

TCP 既要保证可靠性,又要尽可能提高性能。
本篇围绕 TCP 的核心机制展开:滑动窗口、流量控制、拥塞控制、延迟应答、捎带应答、面向字节流与粘包问题。

一、滑动窗口:一次发送多条数据

如果每发一个数据段都要等 ACK,性能会较差,尤其是往返时间较长时。

于是 TCP 引入滑动窗口。

窗口大小指的是:无需等待确认应答而可以继续发送数据的最大值。
例如窗口大小为 4000 字节,即四个段。

发送前四个段时,不需要等待任何 ACK,直接发送。
收到第一个 ACK 后,滑动窗口向后移动,继续发送第五个段的数据,依次类推。

操作系统内核为了维护滑动窗口,需要开辟发送缓冲区来记录当前还有哪些数据没有应答。
只有确认应答过的数据,才能从缓冲区删掉。

窗口越大,网络的吞吐率就越高。

丢包如何处理?

分两种情况:

  1. 数据包已经抵达,ACK 被丢了
    部分 ACK 丢了并不要紧,因为可以通过后续的 ACK 进行确认。

  2. 数据包直接丢了
    发送方会触发超时重传;如果收到多个重复 ACK,也可能触发快速重传。

二、流量控制:别把接收端撑爆

接收端处理数据的速度是有限的。
如果发送端发得太快,接收端的缓冲区就会满,此时再发数据就会丢包。

TCP 支持根据接收端的处理能力,决定发送端的发送速度,这就是流量控制。

接收端如何把窗口大小告诉发送端?
TCP 首部中有一个 16 位窗口字段,就是存放窗口大小信息。

如果接收端缓冲区满了,就会将窗口置为 0。
这时发送方不再发送数据,但是需要定期发送一个窗口探测数据段,使接收端把窗口大小告诉发送端。

问题来了:16 位数字最大表示 65535,那么 TCP 窗口最大就是 65535 字节吗?

实际上,TCP 首部 40 字节选项中还包含了一个窗口扩大因子 M,实际窗口大小是窗口字段的值左移 M 位。

三、拥塞控制:别把网络压垮

虽然滑动窗口能高效可靠地发送大量数据,但如果刚开始就发送大量数据,仍然可能引发问题。
因为网络上有很多计算机,当前网络状态可能已经比较拥堵。
在不清楚网络状态的情况下贸然发送大量数据,很可能雪上加霜。

TCP 引入慢启动机制:先发少量数据,探探路,摸清当前网络拥堵状态,再决定按多大速度传输。

核心概念:拥塞窗口 cwnd。

  • 发送开始时,定义拥塞窗口大小为 1。
  • 每次收到一个 ACK 应答,拥塞窗口加 1。
  • 每次发送数据包时,将拥塞窗口和接收端主机反馈的窗口大小做比较,取较小值作为实际发送窗口。

这种增长速度是指数级别的。“慢启动”只是指初始时慢,但增长速度非常快。

为了不增长那么快,引入慢启动阈值 ssthresh:

  • 当拥塞窗口超过这个阈值时,不再按指数方式增长,而是按线性方式增长。
  • TCP 开始启动时,慢启动阈值等于窗口最大值。
  • 每次超时重发时,慢启动阈值会变成原来的一半,同时拥塞窗口置回 1。

少量丢包,仅触发超时重传;大量丢包,就认为网络拥塞。
当 TCP 通信开始后,网络吞吐量会逐渐上升;随着网络发生拥堵,吞吐量会立刻下降。

拥塞控制,归根结底是 TCP 协议想尽可能快地把数据传输给对方,但又要避免给网络造成太大压力的折中方案。

四、延迟应答与捎带应答

延迟应答

如果接收数据的主机立刻返回 ACK,这时候返回的窗口可能比较小。

例如:

  • 接收端缓冲区为 1M。
  • 一次收到了 500K 的数据。
  • 如果立刻应答,返回的窗口就是 500K。
  • 但实际上可能处理端处理速度很快,10ms 之内就把 500K 数据从缓冲区消费掉了。
  • 如果接收端稍微等一会再应答,比如等待 200ms 再应答,那么返回的窗口大小就是 1M。

窗口越大,网络吞吐量越大,传输效率越高。
目标是在保证网络不拥塞的情况下尽量提高传输效率。

但并非所有包都可以延迟应答:

  • 数量限制:每隔 N 个包就应答一次。
  • 时间限制:超过最大延迟时间就应答一次。
  • 一般 N 取 2,超时时间取 200ms,具体依操作系统不同而有差异。

捎带应答

在延迟应答的基础上,很多情况下客户端和服务器在应用层也是“发一收”的。
例如客户端说“How are you”,服务器回“Fine, thank you”。
这时 ACK 就可以搭顺风车,和服务器回应的数据一起回给客户端,这就是捎带应答。

五、面向字节流与粘包问题

创建一个 TCP 的 socket,同时在内核中创建一个发送缓冲区和一个接收缓冲区。

  • 调用write时,数据会先写入发送缓冲区。
  • 如果发送的字节数太长,会被拆分成多个 TCP 数据包发出。
  • 如果发送的字节数太短,就会先在缓冲区里等待,等到缓冲区长度差不多了,或者其他合适时机再发送。
  • 接收数据时,数据从网卡驱动程序到达内核的接收缓冲区。
  • 应用程序调用read从接收缓冲区拿数据。
  • TCP 连接既有发送缓冲区,也有接收缓冲区,既可以读数据,也可以写数据,这叫全双工。

由于缓冲区的存在,TCP 程序的读和写不需要一一匹配。
写 100 字节可以一次write,也可以 100 次write每次 1 字节。
读 100 字节也可以一次read,或多次read。

粘包问题

粘包问题中的“包”,是指应用层的数据包。

在 TCP 协议头中,没有如同 UDP 一样的“报文长度”字段,但有序号字段。
站在传输层角度,TCP 是一个一个报文过来的,按序号排好序放在缓冲区中。
站在应用层角度,看到的只是一串连续的字节数据。
应用程序不知道从哪个部分开始到哪个部分,是一个完整的应用层数据包。

如何避免粘包?归根结底一句话:明确两个包之间的边界。

  • 对于定长的包,保证每次都按固定大小读取。
  • 对于变长的包,可以在包头位置约定一个包总长度字段。
  • 对于变长的包,还可以在包和包之间使用明确的分隔符。

思考:UDP 是否存在粘包问题?
对于 UDP,如果还没有上层交付数据,UDP 的报文长度仍然在。
同时,UDP 是一个一个把数据交付给应用层,有很明确的数据边界。
使用 UDP 时,要么收到完整的 UDP 报文,要么不收,不会出现“半个”的情况。

六、异常情况与 TCP/UDP 对比

异常情况

  • 进程终止:进程终止会释放文件描述符,仍然可以发送 FIN,和正常关闭没什么区别。
  • 机器重启:和进程终止情况相同。
  • 机器掉电 / 网线断开:接收端认为连接还在。一旦接收端有写入操作,发现连接已经不在了,就会进行 reset。即使没有写入操作,TCP 自己也内置了一个保活定时器,会定期询问对方是否还在。如果对方不在,也会把连接释放。
  • 应用层协议也可能有检测机制,如 HTTP 长连接定期检测,QQ 断线后定期重连。

TCP 与 UDP 对比

TCP 用于可靠传输的情况,如文件传输、重要状态更新。
UDP 用于对高速传输和实时性要求较高的通信领域,如早期 QQ、视频传输,UDP 还可用于广播。

TCP 和 UDP 都是程序员的工具,具体怎么用,要根据需求场景判定。

用 UDP 实现可靠传输(经典面试题)

参考 TCP 的可靠性机制,在应用层实现类似逻辑:

  • 引入序列号,保证数据顺序。
  • 引入确认应答,确保对端收到了数据。
  • 引入超时重传,如果隔一段时间没有应答,就重发数据。
  • 还可以加入滑动窗口、流量控制、拥塞控制等思想。

中篇小结

本篇重点:

  • 滑动窗口:提高吞吐量
  • 流量控制:保护接收端
  • 拥塞控制:保护网络
  • 延迟应答、捎带应答:提高效率
  • 面向字节流与粘包:明确应用层边界
  • 异常情况与 TCP/UDP 对比
  • UDP 实现可靠传输的思路

下一篇将下探到网络层、数据链路层和应用层,重点讲 IP、NAT、ARP、DNS 等关键协议。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询