TCP全称是传输控制协议,位于OSI模型的传输层。在协议栈中,IP协议只负责"尽力而为"地把数据包从源主机送到目标主机,不保证不丢失、不重复、不乱序,更不保证一定送达。TCP的作用就是在这个不可靠的IP网络之上,构建出一条逻辑上完全可靠的端到端数据传输通道。
"可靠传输"并不是说数据百分之百不会丢失,而是说协议通过一整套精密的机制,能够在发生丢包、重复、乱序、比特差错等情况时主动检测并恢复,最终确保接收方拿到的数据与发送方发出的数据完全一致。TCP可靠传输要达成四个核心目标:第一,确保数据无差错,即接收方检测到比特错误后可以要求重传;第二,确保数据不丢失,即发送方能够感知哪些数据段没有被确认并重新发送;第三,确保数据不重复,即接收方能够识别并丢弃已经收到的重复数据段;第四,确保数据有序,即接收方能够将乱序到达的数据段排列成正确的顺序。
这些目标的达成依赖一组精心设计的机制协同工作,主要包括序列号与确认机制、校验和机制、超时重传与快速重传机制、滑动窗口流量控制机制,以及拥塞控制机制。这些机制不是独立运行的,而是像一个团队一样相互配合。序列号解决排序问题,确认机制解决丢包感知问题,校验和解决数据完整性问题,滑动窗口解决收发双方速率匹配问题,拥塞控制则解决整个网络链路的负载问题。理解这些机制的协同关系,是掌握TCP可靠传输原理的关键所在。
TCP把应用层交付的数据流视为一个连续的字节序列,对其中每一个字节进行编号,这个编号就是序列号。发送方从字节流中截取一段封装成TCP报文段,报文段头部中的序列号字段的值是这个报文段所携带数据的第一个字节在整个字节流中的编号。接收方收到报文段后,用确认号字段告诉发送方:我已经正确收到了序列号小于这个确认号的所有数据,下一个期望接收的字节的序列号就是这个确认号。
确认机制中最重要的一个设计理念是累积确认。累积确认的含义是:确认号N不表示"我收到了序列号为N的那个字节",而是表示"我收到了序列号小于N的全部字节,下一个期望的是序列号为N的字节"。这种累积语义极大简化了协议设计,发送方只需要关心最小的未被确认的序列号即可。即使某个确认报文丢失了,后续更大序列号的确认同样可以覆盖它。举个例子:发送方发出了序列号1001、2001和3001的三个报文段,如果对2001的确认丢失了,但对3001的确认到达了,发送方就知道序列号小于3001的所有数据都已安全送达。如果确认报文本身在网络中丢失了,只要后续有新确认到达,之前丢失的确认就被自然补上,协议不需要为确认报文本身设计重传机制。
"确认号"这个名称本身就容易引起误解。如果把确认号理解为"已确认收到的字节号",那就完全理解反了。确认号始终指向尚未收到的最低字节序号,它既是对过去的确认,也是对未来的期望。在软考选择题中,命题人非常喜欢在这个语义上做文章。比如题干说某TCP报文携带了100字节的数据、序列号为2001,问接收方返回的确认号是多少,正确选项是2101而不是2100。很多考生因为记错了确认号的语义而丢分,务必牢记确认号是一个指向前方的指针而不是指向后方的总结。
除了序列号和确认号之外,TCP还在每个报文段的头部包含一个16位的校验和字段。校验和的计算覆盖了整个TCP报文段以及一个伪首部,伪首部包含源IP地址、目的IP地址、协议号和TCP报文段长度。把IP层信息纳入校验范围,是为了防止数据在IP层被错误路由到错误的主机或错误的端口时不被察觉。校验和虽只能检测不能纠正比特错误,但它是TCP可靠性的第一道防线。接收方计算出的校验和与报文段中不一致时直接丢弃该报文段且不发送确认,发送方因未收到确认最终触发超时重传。一个值得留意的细节:IPv4首部校验和在实践中是可选的,而TCP校验和是强制性的,这足以说明TCP对数据完整性的重视程度。
确认机制解决的是"如何告诉发送方数据收到了"的问题,但更深层的问题是:如果数据根本没到接收方,或者确认丢失了,发送方怎么知道?这就是超时重传机制的核心命题。
发送方每发出一个报文段就启动一个重传定时器。如果定时器到期前收到了确认则取消定时器,如果到期时仍未收到确认则重发相同数据并加倍超时时间。这种指数退避策略是为了防止在已拥塞的网络中过于频繁的重传进一步恶化状况。
超时定时器的时长设置是TCP性能的关键。设得太短会导致不必要重传浪费带宽,设得太长则丢包后迟迟得不到恢复。TCP采用了一种动态自适应的方法来计算超时时间,核心是实时测量往返时间。每当发送方收到一个确认,它就会根据发送时间和确认到达时间计算出这次通信的一次往返时间样本值。TCP并不直接使用单个样本值,因为网络状况是波动的,单次测量值可能过于乐观或悲观。TCP维护一个平滑往返时间估计值,通过指数加权移动平均的方法对多个样本进行平均,使得估计值既能够跟踪网络状况的变化,又不会因为偶然的波动而剧烈抖动。
有了往返时间估计值之后,还需要计算实际使用的超时时间。TCP引入了往返时间偏差的概念来衡量估计值的稳定性,超时时间等于平滑往返时间估计值加上四倍的偏差值。当网络状况稳定时偏差很小,超时时间接近估计值本身;当网络抖动剧烈时偏差增大,超时时间自动拉长,从而避免不必要重传。
超时重传是基本手段但效率不高,因为发送方必须等到定时器到期才能发现丢包。在实际运行中TCP有一种更高效的重传触发方式叫快速重传。它的工作原理依赖于TCP对乱序报文段的处理方式。当接收方收到一个序列号大于期望值的报文段时,说明中间有数据段没有到达。按照TCP的规定,接收方会立即发送一个确认,确认号为它仍然期望的下一个字节的序列号,也就是那个尚未到达的数据段的序列号。如果后续又有乱序报文段到达,接收方会再次发送相同的确认号。这样一来,发送方就会连续收到多个完全相同的确认。当发送方连续收到第三个重复确认时,它推断对应的数据段大概率已经丢失了,于是在定时器到期之前就立即重传那个数据段。
为什么是三个重复确认而不是一个或两个?因为网络中的数据包可能因为路由路径的不同而自然乱序,偶尔的一两个乱序报文触发的重复确认并不一定意味着丢包。三个重复确认是一个经验阈值,在这个数量下能够以很高的概率判定丢包已经发生,同时误判概率又足够低。这是TCP在快速反应和避免误判之间的精巧平衡。
确认重传解决了丢包后的数据恢复问题,但还有一个更基础的问题需要解决:如果发送方的发送速率远远超过接收方的处理能力,接收方的缓冲区会被填满,后续到达的数据就只能被丢弃。这种因为收发速率不匹配导致的丢包完全可以通过让发送方和接收方进行速率协商来避免。TCP的解决方案就是滑动窗口流量控制机制。
滑动窗口的核心思想是接收方通过确认报文告诉发送方自己还剩多少缓冲区空间,发送方据此控制未确认数据的最大量,这个上限就是发送窗口。但发送方还要考虑拥塞窗口的限制,实际使用的发送窗口是接收窗口和拥塞窗口中的较小值。
窗口的滑动是TCP协议最精妙的设计之一。发送窗口的左边界是被确认的最小序列号,右边界等于左边界加上窗口大小。当发送方收到新的确认时,左边界向右移动,同时右边界也向右移动同样的距离,就好像窗口沿着字节流向前滑行一样。窗口之内的数据是可以发送的,窗口之外的数据则暂时不能发送。已经发送但尚未确认的数据同时占用窗口空间,当它们被确认后窗口空间释放出来可以用于发送新的数据。这种设计使得确认不仅告知了接收情况,同时赋予了发送方发送新数据的能力,实现了可靠传输和流量控制的一体化。
如果接收方的应用程序处理数据非常慢,接收缓冲区逐渐被填满,接收方通告的窗口大小可能变为零。零窗口意味着接收方暂时无法接收任何新数据,发送方必须停止发送并等待。但如果接收方后来腾出空间后发送的新窗口通告丢失了,发送方永远等不到非零窗口通告,接收方永远等不到新数据,双方陷入死锁。TCP用持续定时器来解决这个问题。收到零窗口通告后发送方启动持续定时器,到期时发送一个仅携带一个字节数据的探测报文段。若接收方仍通告零窗口则重新计时;若窗口已打开则
本篇完!