运输层:TCP 可靠数据传输
禁止码迷,布布扣,豌豆代理,码农教程,爱码网等第三方爬虫网站爬取!
序号和确认号
TCP 报文中最重要的 2 个字段是序号和确认号,这两个字段是 TCP 实现可靠数据传输的关键。TCP 认为数据是无结构、有序的字节流,序号是建立在传送的字节流上,而不是建立在传送的报文段上,因此序号是一个报文段的首字节的字节流编号。接下来讲解确认号,报文段的确认号是源主机希望从目的主机收到的下一字节的序号。由于 TCP 仅确认流中第一个丢失字节之前的字节,该字节对应的序号之前的所有字节都被收到,因此 TCP 的确认方式也被称之为累积确认。
在实际情况下,一条 TCP 连接的双方均可随机地选择初始序号,这么做可以减少存在于两台主机先前已终止的连接中的残余报文段被传输的可能性。当接收方收到序号是乱序的报文段时应该怎么处理?TCP 标准中并没有明确地规定,所以这个问题将交给程序员来解决。实践中接收方会保留失序的字节,并且等待缺少的字节来填补完整。
往返时间和超时重传
TCP 使用超时/重传机制来处理丢包问题,每发送一个报文段,就对这个报文段设置一次计时器。只要计时器设置的重传时间到但还没有收到确认,就要重传这一报文段。如果把超时重传时间设置得太短,就会引起很多报文段的不必要的重传,使网络负荷增大。但若把超时重传时间设置得过长,则又使网络的空闲时间增大,降低了传输效率。合适的重传时间应该如何设置?
往返时间估计
计时器的超时间隔设置是个困难的问题,首先超时时间间隔必须大于该链接的往返时间(RTT)。TCP 采用了一种自适应算法,通过 RTT 样本计算出加权平均往返时间 RTTs。计算方式为第一次测量到 RTT 样本时,RTTS 值就取为所测量到的 RTT 样本值。以后每测量到一个新的 RTT 样本,就按下式重新计算一次 RTTS:
式中 0 ≤ a ≤ 1,若 a 很接近于零表示 RTT 值更新较慢,若选择 a 接近于 1 则表示 RTT 值更新较快。RFC 2988 推荐的 a 值为 1/8,即 0.125。
超时重传时间
RTO (Retransmission Time-Out) 应略大于上面得出的加权平均往返时间 RTTs,RFC 2988 建议使用下式计算 RTO:
RTTd 是 RTT 的偏差的加权平均值,RFC 2988 建议第一次测量时,RTTd 值取为测量到的 RTT 样本值的一半。在以后的测量中,则使用下式计算加权平均的 RTTd:
此处需要强调的是,对重传分组的 ACK 计算 RTT 会导致 RTTs 和 RTO 数据变得不够准确,将会与符合现实情况的 RTO 产生较大的偏差。因此在计算平均往返时间 RTT 时,只要报文段重传,就不采用其往返时间样本。
可靠数据传输
TCP 在 IP 不可靠的尽力而为的服务之上,创建了可靠数据传输服务。我们原来的想法是每一个已发送但并未确认的报文段都配备一个定时器,但是定时器的管理需要巨大的开销。因此即使有多个已发送但是未确认的报文段,还是仅使用单一的重传定时器。
TCP 发送方
TCP 发送方有 3 个与发送和重传有关的主要事件:从上层应用程序收到数据、定时器超时、收到 ACK。TCP 从应用程序接收数据,将数据封装在一个报文段中,并把数据交给 IP 协议。每一个报文段都包含一个序号,序号是该报文段第一个数据字节的字节流编号。若定时器还没有为某些其他报文段而运行,则报文段传到 IP 时 TCP 就启动定时器。
第二个事件是超时,TCP 通过重传引起超时的报文段来响应超时事件,然后 TCP 重启定时器。
第三,当发送方收到一个包含有效 ACK 字段值的报文段,则 TCP 将 ACK 的值和最早未被确认字节的序号(SendBase)进行对比。TCP 采用累积确认,所以确认号确认了在确认号之前的所有字节都已经收到。如果确认号数值大于 SendBase,则该 ACK 在确认一个或多个先前未被确认的报文段,发送方就据此更新 SendBase。若当前还有未被确认的报文段,TCP 还需要重启计时器。
伪代码
NextSeqNum = InitialSepNumber;
SendBase = InitialSepNumber;
while(1)
{
switch 事件:
{
case 从应用程序接收到数据:
生成具有序号 NextSeqNum 的 TCP 报文段;
if(定时器当前没有运行)
启动定时器;
向 IP 协议传递报文段;
NextSeqNum = NextSeqNum + length(data); //更新序列号
break;
case 定时器超时:
重传具有最小序号但仍未应答的报文段;
重启定时器;
break;
case 收到 ACK,ACK 字段值为 y:
if(y > SendBase)
{
SendBase = y;
if(当前仍无任何应答报文段)
启动定时器;
}
break;
}
}
情景分析
首先看第一个情景,发送方向接收方发送一个报文段,但是接收方发送的 ACK 发生了丢包。此时发送方将重传分组,当接收方发现该分组曾经收到过,因此接收方就会丢弃重传报文段中的这些字节。
第二个情景,发送方向接收方发送一个报文段,但是接收方发送的 ACK 都延迟到达发送方。由于超时时间发生,发送方重传第一个分组,同时重启定时器。接收方收到重复的报文段时,发送的还是第二个报文段的 ACK。只要第二个报文段的 ACK 在新的超时发生前到达,就不会被重传。
第三个情景,发送方向接收方发送一个报文段,但是接收方发送的第一个报文段的 ACK 延迟到达发送方。但是在超时事件发生前,发送方若接收到了第二个报文段的 ACK,则说明第一个报文段也被收到了,就不会进行重传。
超时间隔加倍
接下来考虑一个新的问题,当报文段的时延突然增大了很多时,在原来得出的重传时间内不会收到确认报文段。于是就重传报文段。但是在计算超时间隔时间时,不考虑重传的报文段的往返时间样本,这样超时重传时间就无法更新。因此 TCP 在实际中实现时,报文段每重传一次,就把 RTO 增大一些,系数 γ 的典型值是 2。当不再发生报文段的重传时,才根据报文段的往返时延更新平均往返时延 RTT 和超时重传时间 RTO 的数值。
这种修改提供了一种形式受限的拥塞控制,因为在拥塞的时候持续地重传分组,会使得拥塞更为严重。
快速重传
超时重传可能会引发超市周期相对较长的问题,也就是重发丢失分组前要等很长时间。若某个分组发生丢包,可能发生多个重复的 ACK,这些冗余 ACK 可以用于检测丢包事件的发生。
接收方动作
事件 | TCP 接收方的动作 |
---|---|
期望序号的报文按序到达,且之前数据都已确认 | 延迟的 ACK,对下一个报文段等待 500 ms,若没到达则发送 ACK |
期望序号的报文按序到达,另一个按序报文段等待 ACK 传输 | 立即发送单个累积 ACK,确认 2 个按序报文段 |
比期望序号大的失序报文段到达 | 立即发送冗余 ACK,指示下一个期待字节的序号 |
能部分或完整填充接收数据建个的报文段到达 | 倘若该报文段起始于间隔的低端,则立即发送 ACK |
发送方动作
发送方若接收到对相同数据的 3 个冗余 ACK,则说明可能跟在这个已被确认 3 次的报文段之后的报文段发生了丢包。此时 TCP 将执行快速重传,即在该报文段的定时器过期之前重传丢失的报文段。
case 收到 ACK,ACK 字段值为 y:
if(y > SendBase)
{
SendBase = y;
if(当前仍无任何应答报文段)
启动定时器;
}
else
{
y 对应的冗余 ACK 数 += 1;
if (冗余 ACK 数 == 3) //快速重传
重新发送具有序号 y 的报文段;
}
break;
回退 N 步还是选择重传?
考虑一个问题,TCP 是一个 GBN 协议还是 SR 协议?TCP 使用的是累积确认,正确但是失序的报文段不会被接收方逐个确认,这个特性使得 TCP 看上去是一个 GBN 协议。考虑一个情景,当发送方发送多个报文段,第二个数据段发送丢包造成失序,但是后续的所有报文段都被成功接收。但是由于累积确认,后续的报文段的 ACK 都会要求发送方传输第二个报文段,这就可能导致大量的不必要重传。
对 TCP 提出的一种修改意见就是选择确认,它允许 TCP 接收方有选择地确认失序报文段。接收方收到了和前面的字节流不连续的两个字节块,如果这些字节的序号都在接收窗口之内,那么接收方就先收下这些数据,但要把这些信息准确地告诉发送方,使发送方不要再重复发送这些已收到的数据。
当选择确认机制和选择重传机制结合起来使用时,TCP 也会看起来像是 SR 协议。因此 TCP 的差错恢复机制,可以被认为是 GBN 协议和 SR 协议的结合体。
参考资料
《计算机网络(第七版)》 谢希仁 著,电子工业出版社
《计算机网络 自顶向下方法》 [美] James F.Kurose,Keith W.Ross 著,陈鸣 译,机械工业出版社