跳转到内容
← 返回核心概念
系统与架构计算机科学 · 计算机网络16 分钟阅读

TCP 与可靠传输

TCP and Reliable Transport

1986 年秋,互联网发生了所谓的"拥塞崩溃":ARPANET 的吞吐量骤降至峰值的 0.1%。流量挤满了网络,但几乎没有有用的数据成功到达。这不是硬件故障,而是协议设计的缺陷——当时的 TCP 没有任何机制限制发送速率。工程师 Van Jacobson 花了几个星期设计出拥塞控制算法,写了几百行代码,将 ARPANE…

TCP可靠传输拥塞控制流量控制

1986 年秋,互联网发生了所谓的"拥塞崩溃":ARPANET 的吞吐量骤降至峰值的 0.1%。流量挤满了网络,但几乎没有有用的数据成功到达。这不是硬件故障,而是协议设计的缺陷——当时的 TCP 没有任何机制限制发送速率。工程师 Van Jacobson 花了几个星期设计出拥塞控制算法,写了几百行代码,将 ARPANET 从混沌边缘拉回来。这个故事说明:在共享网络上做可靠传输,远比表面看起来难得多。

破除误解:可靠不是"不丢包"

很多人以为 TCP"可靠"意味着数据不会丢失。实际上,底层的 IP 网络随时可能丢包、乱序、重复——TCP 的工作是在不可靠的 IP 上构建可靠的字节流。可靠性是通过复杂的协议机制实现的:确认、重传、排序。底层丢包仍然发生,只是应用程序无需关心。

更要厘清的是:TCP 的"可靠"只保证字节流不丢、不重、不乱序,它既不等于"安全",也不等于"低延迟"。

TCP 报文默认明文传输,本身不做加密与身份认证——这些交给其上的 TLS。"用了 TCP 所以安全"是误解。它也不保证低延迟:为了按序交付,一个段丢失就会拖住后面已到达的数据(详见后文的队头阻塞),这正是实时音视频宁可改用 UDP 的原因。

这套设计可追溯到 1974 年。Vinton Cerf 与 Robert Kahn 在 IEEE Transactions on Communications 发表《A Protocol for Packet Network Intercommunication》,提出互联异构网络的传输控制协议——当时 TCP 与 IP 还是一个协议,后来才拆成传输层与网络层。1981 年 Jon Postel 主编的 RFC 793 正式定义了今天的 TCP,这份文档一直服役到 2022 年才被 RFC 9293 取代,后者把散落在十几个 RFC 中的修订收拢成一份完整规范。

核心:三次握手与连接状态

TCP 是面向连接(connection-oriented)的协议。通信双方必须先建立连接,再传数据,最后关闭连接。连接建立通过三次握手完成:

客户端 --SYN(seq=x)---------> 服务端
客户端 <--SYN-ACK(seq=y,ack=x+1)-- 服务端
客户端 --ACK(ack=y+1)-------> 服务端
      (连接建立,数据传输开始)
```
  • SYN(Synchronize):客户端选择一个初始序号 $x$,请求建立连接。
  • SYN-ACK:服务端确认,同时选择自己的初始序号 $y$
  • ACK:客户端确认服务端的序号,连接建立。

三次握手不是繁文缛节,而是必要的:它确保双向通信路径都可用,并同步双方的初始序号,防止历史连接的旧数据包干扰新连接。

初始序号(ISN)也不能随便选。若用可预测的值,off-path 攻击者就能猜中序号、伪造数据包注入连接。RFC 6528(2012,Fernando Gont 与 Steven Bellovin)规定 ISN 由一个微秒级时钟值,加上对"源/目的 IP、源/目的端口、密钥"做密码学散列得到,外人难以预测——握手既是同步,也是一道安全门槛。

握手的代价是延迟:要花一个完整 RTT,数据才能开始传。再叠加其上的 TLS,新连接往往要 2 到 3 个 RTT 才能发出第一个字节。后文的 TCP Fast Open 与 QUIC,瞄准的正是这笔握手延迟。

序号、确认与重传

TCP 把数据视为字节流,每个字节都有序号。发送方把数据分成段(Segment)发送;接收方用 ACK 告知已收到哪些字节:

ACK=下一个期待的字节序号\text{ACK} = \text{下一个期待的字节序号}

若发送方在超时时间内未收到 ACK,则重传对应的段。超时时间(RTO,Retransmission Timeout)并非固定值,而是根据往返时间(RTT)的测量动态调整:

RTO=SRTT+4×RTTVAR\text{RTO} = \text{SRTT} + 4 \times \text{RTTVAR}

其中 SRTT 是平滑化的 RTT 估计,RTTVAR 是 RTT 方差。这一公式由 Jacobson 在 1988 年提出,成为所有现代 TCP 实现的标准。

更精确地说,RFC 6298(2011)把它标准化为 RTO=SRTT+max(G,K×RTTVAR)\text{RTO} = \text{SRTT} + \max(G, K \times \text{RTTVAR}),其中 $K=4$、平滑系数 α=1/8\alpha=1/8β=1/4\beta=1/4$G$ 是时钟粒度——这些常数正源自 Jacobson 与 Karels 1988 年的工作。

逐字节累计确认有个弱点:一旦中间丢了一个段,发送方只知道"卡在这里了",却不知道后面哪些段其实已经到达,容易整窗重传。RFC 2018(1996,Mathis 等)的选择性确认(SACK)让接收方在 ACK 里附带已收到的不连续区块,发送方便只补真正缺失的段,在一窗多丢时大幅提速。

另一处现代调整是起始速度。早期 TCP 慢启动只从 2 到 4 个段起步;RFC 6928(2013,Google 的 Dukkipati 等)把初始拥塞窗口提到 10 个段(IW10),让多数网页在一两个 RTT 内就能基本传完,如今已被广泛采用。

流量控制与滑动窗口

若发送方比接收方快,接收方的缓冲区会溢出。TCP 用接收窗口(rwnd)来解决:接收方在每个 ACK 中告知自己还有多少缓冲空间,发送方不得超出这个范围:

发送方已发但未确认的数据量 <= rwnd
```

这种机制叫滑动窗口(Sliding Window)。窗口可以随接收方处理能力动态变化,接收方缓冲区满时可将 rwnd 设为 0,让发送方暂停。

拥塞控制:互联网的稳定器

流量控制解决了"不压垮对方",但没有解决"不压垮网络"。当许多 TCP 连接同时工作,路由器队列会溢出导致丢包——这正是 1986 年崩溃的原因。

那次崩溃的细节很惊人:1986 年 10 月,相距仅约 400 米、中间只隔三跳的劳伦斯伯克利实验室(LBL)与加州大学伯克利分校之间,吞吐量从 32 kbps 跌到 40 bps,掉了近 800 倍。Van Jacobson 与 Mike Karels 追查此事,成果便是 1988 年那篇奠基性的《Congestion Avoidance and Control》。

TCP 拥塞控制的核心思想:把丢包视为网络拥塞的信号,主动降低发送速率

主要算法阶段:

阶段机制效果
慢启动(Slow Start)每收到一个 ACK,拥塞窗口(cwnd)加倍指数增长,快速探测可用带宽
拥塞避免(Congestion Avoidance)达到慢启动阈值后,每 RTT 加 1 MSS线性增长,温和试探
快速重传(Fast Retransmit)收到 3 个重复 ACK,立即重传,不等超时更快检测单包丢失
快速恢复(Fast Recovery)快速重传后,减半 cwnd 而非归零避免过度降速

这套算法不是一次成型的。1988 年的 4.3BSD-Tahoe 首次实现慢启动、拥塞避免与快速重传;1990 年的 4.3BSD-Reno 才补上快速恢复,"Tahoe / Reno" 也因此成了拥塞控制版本的代号。

这里要厘清一个常见误读:慢启动里 cwnd 并非"每收到一个 ACK 就翻倍",而是每个 ACK 增加 1 个 MSS,于是每经过一个 RTT(一整窗的 ACK 回来)才翻一倍——指数增长是按轮次算的,不是按单个 ACK 算的。

这种"加性增、乘性减"(AIMD)的策略,被证明在数学上是收敛的:多条 TCP 连接共享带宽时,最终趋向公平分配。这是互联网能在无中央控制下稳定运行的数学基础之一。

经典 AIMD(即 TCP Reno)在今天的高带宽长距离链路上太保守:一次丢包就把窗口砍半,恢复又慢。于是 Linux 自 2006 年的 2.6.19 内核起,默认改用 CUBIC(Sangtae Ha、Injong Rhee、Lisong Xu,RFC 8312,后由 RFC 9438 转入标准轨):它用时间的三次函数控制窗口增长,与 RTT 解耦,在大带宽时延积下既能快速占满带宽,又对短链路保持温和。

更激进的是 Google 2016 年提出的 BBR(Neal Cardwell、Yuchung Cheng、Van Jacobson 等)。它不再"把丢包当拥塞信号"——在无线和深缓冲网络里,丢包未必意味着拥塞——而是主动测量瓶颈带宽与最小往返时延,按二者乘积调速。在部分高丢包路径上,BBR 的吞吐能比 CUBIC 高出数倍。这也带来新争议:BBR 与传统基于丢包的算法共享同一链路时可能抢占更多带宽,公平性仍在持续研究中。

TCP 与 UDP 的取舍

TCP 的可靠性是有代价的:握手延迟、重传延迟、拥塞控制降速。对一些应用,这些代价不可接受:

场景选择原因
网页加载、文件传输、电子邮件TCP数据完整性第一
视频通话、在线游戏、直播UDP低延迟优先,少量丢包可接受
DNS 查询UDP(小包)/ TCP(大响应)单次请求/响应,握手成本太高
QUIC/HTTP/3UDP + 应用层可靠性自建可靠传输,规避 TCP 的固有延迟

代价与争议

"TCP 过时了":TCP 在 1981 年标准化(RFC 793),设计假设有线网络为主。移动网络兴起后,无线丢包(信号问题)被误认为是拥塞,触发不必要的速率下降。QUIC 的出现部分源于此。

QUIC 真正想根治的,是 TCP 的队头阻塞(head-of-line blocking)。TCP 是单一有序字节流,接收方必须按序交付:哪怕第 500 字节后面的数据早已到达,只要第 500 字节所在的段还没补到,应用就一个字节也读不到。HTTP/2 在一条 TCP 连接上多路复用多个请求,反而放大了这个问题——一个段丢失会卡住连接上所有的流。

QUIC(RFC 9000,2021 年 5 月)把传输层搬到 UDP 之上,自带 TLS 1.3,并把数据切成多条相互独立的流:某条流丢包只阻塞它自己,其余流照常交付。它还把 TCP 三次握手与 TLS 握手合并,新连接 1 个 RTT 即可发数据(TCP+TLS 通常要 2 到 3 个),重连可达 0-RTT。HTTP/3(RFC 9114,2022 年 6 月)就建立在 QUIC 之上。

公平性假设的局限:AIMD 公平性依赖所有连接都使用 TCP。若一方改用 UDP 应用层保证可靠性而不做拥塞控制,它可以抢占更多带宽,损害其他 TCP 连接。这是网络生态的"公地悲剧":个体理性导致集体亚优。QUIC 的拥塞控制设计正是为了避免成为新的"不守规则的参与者"。

TCP Fast Open(TFO):2014 年标准化的扩展,允许在第一个 SYN 包中携带数据,把握手延迟从 1 RTT 降为 0 RTT(连接复用时)。部署受中间件(防火墙、NAT)的阻碍,普及缓慢——这说明互联网协议升级的最大障碍往往不是技术,而是基础设施的惰性。

准确说,它是 RFC 7413(2014 年 12 月,Google 团队)定义的实验性扩展,并非正式标准轨:服务端先发一个加密 cookie,客户端缓存后在后续连接的 SYN 里带上 cookie 与数据。代价是安全语义被削弱——SYN 中的数据在极端情况下可能被重放,这也是它始终停留在实验状态的原因之一。

跨域连接

  • 公地悲剧:带宽是可竞争、难排他的公共资源,个体最优是尽量快发,集体后果就是正文那次吞吐跌到峰值千分之一的崩溃。关键在于拥塞控制的效力来自自愿遵守而非协议强制——任何不做拥塞控制的参与者都能抢占份额。这正是新传输协议必须自带拥塞控制的原因:否则它就成了新的搭便车者。
  • 动力系统:加性增乘性减为什么收敛到公平分配,在二维带宽分配图上看得最清楚:乘性减沿原点方向缩回,保持两条连接的比例;加性增沿对角方向推进,缩小它们的绝对差。反复迭代因而趋向等分线。推论是这套算法的形式不可随意改动——换成加性减或乘性增,收敛性立刻消失,参数却可以调。
  • 贝叶斯定理:把丢包当拥塞信号,是把一个有噪声的观测当成确定指示。有线网络里丢包的先验原因几乎只有队列溢出,这个近似很准;无线链路上信号质量也会致丢包,同一观测的后验含义就变了,于是降速降错了地方。改用瓶颈带宽与最小往返时延作可观测量,本质是换一个在新环境下更有信息量的信号。
  • 万维网与 HTTP:队头阻塞暴露了一条一般规律:抽象层提供的强保证在被复用时会变成过度约束。单一有序字节流承诺按序交付,一旦在其上多路复用多个逻辑流,一个段丢失就把所有流一起停住,而应用其实并不要求跨流有序。推论是保证越强越难在其上重建更弱的语义,只能把有序性下放到流粒度重做。
  • 演化发育生物学:协议僵化与发育早期基因的高度保守同因——越多下游依赖某个接口的当前行为,改动它的代价越高,于是变异被清除。这解释了迁移速度的巨大差异:应用层协议几年一换,网络层的地址协议三十年未完成替换。推论是可演化性能够被设计:把路径上可被解析的字段减到最少,就减少了依赖它的下游。

参考文献

  • Cerf, V. & Kahn, R. A Protocol for Packet Network Intercommunication. IEEE Transactions on Communications, vol. 22, no. 5, pp. 637–648, 1974.
  • Jacobson, V. Congestion Avoidance and Control. ACM SIGCOMM, 1988.
  • Stevens, W. TCP/IP Illustrated, Vol. 1. Addison-Wesley, 1994.
  • Postel, J. Transmission Control Protocol. RFC 793, IETF, 1981.
  • Eddy, W. (Ed.). Transmission Control Protocol (TCP). RFC 9293, IETF, 2022(obsoletes RFC 793).
  • Paxson, V., Allman, M., Chu, J. & Sargent, M. Computing TCP's Retransmission Timer. RFC 6298, IETF, 2011.
  • Mathis, M., Mahdavi, J., Floyd, S. & Romanow, A. TCP Selective Acknowledgment Options. RFC 2018, IETF, 1996.
  • Rhee, I., Xu, L., Ha, S., et al. CUBIC for Fast and Long-Distance Networks. RFC 9438, IETF, 2023(obsoletes RFC 8312).
  • Cardwell, N., Cheng, Y., Gunn, C. S., Hassas Yeganeh, S. & Jacobson, V. BBR: Congestion-Based Congestion Control. ACM Queue, vol. 14, no. 5, 2016.
  • Iyengar, J. & Thomson, M. (Eds.). QUIC: A UDP-Based Multiplexed and Secure Transport. RFC 9000, IETF, 2021.
  • Bishop, M. (Ed.). HTTP/3. RFC 9114, IETF, 2022.