计算机网络:运输层(2)

一、TCP可靠传输原理

网络层的IP协议仅做到尽最大努力交付。TCP协议必须采取一些措施才能使传输变得可靠。

1. 理想传输条件

理想传输条件下,不需要任何措施就可以实现可靠传输:

  1. 传输信道不产生差错
  2. 无论发送方以多快的速度发送数据,接收方总是来得及处理收到的数据

实际的网络不可能满足这两个条件。因此,TCP协议需要解决这两个问题,以保证可靠传输。

2. 停止等待协议

全双工信道的双方既是发送方,也是接收方。因此将发送TCP报文的称为发送方,而接收报文并回送确认的称为接收方
停止等待协议就是每发送一个TCP分组后,等待对方回送确认,再发送下一个分组。
假设一个TCP连接中,端口A向端口B发送报文。下面分析停止等待协议中出现的情况:

  1. 全无差错
    在全无差错的情况下,A发送分组M1,发送完成后暂停发送,等待B收到分组后,回送确认;A在收到B发回的确认后,再发送下一个分组。
    无差错情况
  2. 分组发送出现差错
    分组出现差错可以分为分组丢失分组出错两种情况。无论哪种,B都不会回送确认。
    为解决此问题,A中设有超时计时器,每当发送一个分组后,就要设置超时计时器开始计时。若在超时前收到了确认,即无差错情况,则撤销超时计时器;若在规定的时间内没有收到确认,就将此前的报文重传一次。这就是超时重传机制
    超时重传
    由此需要注意:
    ● 发送分组后,发送端需要暂时保留已发送的副本,留作重发使用。收到确认后才将副本清除。
    ● 分组和确认分组需要进行编号,明确收到确认的分组和没有收到确认的分组。
    ● 超时计时器的超时时间需要比平均往返时间稍长,具体的设置受大量不确定因素影响,将在实现部分讨论。
  3. 确认出现差错
    B中收到了分组,但是回送的确认出现了差错。这里可以分成两种情况:
    确认丢失:发送的确认发生了丢失的情况。此时A认为分组没有成功发送,会重发一次。当B收到重发的分组后,做两项操作:丢弃重复的分组重新向A发送确认
    确认丢失
    确认迟到:发送的确认由于网络状况的原因,确认在超时后才到达。
    对于B而言,需要进行的操作不变;而对A而言,将会收到重复的确认。对于重复的确认,只需要丢弃即可

利用上面所述的确认重传机制,可以实现在不可靠的传输网络上进行可靠的通信
这样的可靠传输协议常称为自动重传请求

信道利用率
停止等待协议的优点是实现简单,但缺点是信道利用率太低。
假设A和B之间有一条直通的信道传输分组:
信道利用率表示
A发送分组的时间是TD,B发送确认的时间是TA,往返时间为RTT。忽略接收分组的处理时延和发送确认的等待时延,发送分组共需要的时间为TD+RTT+TA。而其中只有TD的时间在传送有用的数据,因此信道利用率可以表示为:
U = TD / (TD+RTT+TA)
一般来说,TA << TD,因此信道利用率主要取决于RTT的大小,而RTT的大小则取决于信道特性。
当RTT >> TD时,信道的利用率就会非常低。如果出现差错重传,信道利用率会变得更低。

3. 连续ARQ协议

为了提高传输效率,应该将低效率的停止等待协议更换为流水线传输的方法。
流水线传输
为了对流水线传输加以可靠控制,需要使用连续ARQ协议滑动窗口协议
滑动窗口协议较为复杂,是TCP协议的精髓所在。发送方维持一个发送窗口,在发送窗口内的分组,都可以连续地进行发送,由此提高信道利用率。每当发送方收到一个分组的确认(应当是最早发送的),就将发送窗口向前滑动一个分组的位置。
滑动窗口
对接收方而言,需要采用累积确认的方式。接收方不必对每一个分组都发送确认,只需要对按序到达的最后一个分组发送确认,发送方收到该确认时,就认为到该分组之前的分组均已经正确接收了。这种方式可以避免部分确认丢失造成的重传损耗。
然而,累积确认带来的问题是,不能正确地反映接收端接收分组的情况。例如当接收的分组未能按序到达,中间部分丢失时,接收端不能发送后半部分接收到分组的确认。因此,发送端在超时后,将会重发从第一个丢失的分组开始的所有分组,造成浪费。这种回退到未接收分组进行重传的机制叫做回退N*(Go-Back-N)
由此可见,当网络质量较差时,连续ARQ协议会造成负面影响

二、TCP报文的首部格式

TCP虽然是面向字节流的,但数据单元是报文段。一个TCP报文段分为首部数据两部分字段。TCP的全部功能都体现在它首部中的各字段中。下面是TCP报文段的首部格式:
TCP报文段首部
TCP报文段首部的前20个字节是固定的,后面4n字节是可根据需要而增加的选项。因此TCP报文首部的最小长度是20字节。
各字段意义如下:

  1. 源端口和目的端口:各占2字节,用于TCP的复用和分用,与UDP相似。
  2. 序号:占4字节,共232-1个序号,是本报文段发送的第一个字节在字节流中的位置(按序编号)。当序号用完后重新从0开始,即序号 = 字节流的编号 mod 232
  3. 确认号:占4字节,是期望收到的下一个报文段的第一个数据字节的序号。若确认号为N,表示N-1之前的字节都成功收到。
    一般来说,当下一次的序号重复时,已经过去了4GB的数据,旧的序号应该已经传输完成了。
  4. 数据偏移:占4位,指出TCP报文段起始位置到数据部分起始位置的距离,因此实际上是首部长度。其单位为32位4字节,最小为5(20字节),最大为15(60字节),即选项长度最大不超过40字节。
  5. 保留:占6位,留作今后使用,目前置0。
  6. 控制位:下面有6个单位的控制位,指明本报文段的性质:
    URG:即紧急位。URG = 1时表示该报文紧急指针有效,通知系统此报文有紧急数据,应尽快处理。
    ACK:即确认位。ACK = 1时表示该报文确认号字段有效;否则无效。TCP规定建立连接后所有报文段的ACK都置1。
    PSH:即推送位。PSH = 1时表示收到该信息后立即发送或交付给进程,而不是等待到缓存填满。
    RST:即重置位。RST = 1时表示TCP连接出现严重差错,必须释放连接,重新建立;或者用于拒绝非法报文或连接。
    SYN:即同步位。SYN = 1且ACK = 0时表示一个连接请求报文段;SYN = 1且ACK = 1表示接收连接请求。
    FIN:即终止位。FIN = 1时表明此报文的发送方数据已经发送完毕,要求释放连接。
  7. 窗口 :占2字节,是[0,28-1]的整数。该字段是本报文段发送方的接收窗口,作为对方设置发送窗口的依据。
  8. 校验和:占2字节,与UDP类似,也需要加上伪首部。
  9. 紧急指针:占2字节,指出报文段中紧急数据的字节数。在处理完紧急数据后,应用恢复到正常操作。当窗口为0也可以发送紧急数据。
  10. 选项与填充:长度可变,最大40字节,且必须是4字节的整数倍。不足的部分将用0填充。

比较常见的选项字段:
● 最大报文段长度MSS:TCP报文段数据字段的最大长度。若TCP数据部分过短或过长都会降低传输效率,且路径是动态变化的,因此很难确定最佳MSS,只能尽可能大,且不需要分片。
● 窗口扩大选项:占3字节,用于扩大最大窗口。一个字节表示移位值S,新的窗口字段长度等于(16+S),S允许的最大值为14,相当于窗口的最大值增大到230-1。
● 时间戳选项:占10字节,主要是时间戳值字段时间戳送回字段(各4字节)。具有两个功能:计算往返时间RTT防止序号绕回
选择确认选项

三、TCP可靠传输实现

为简便讨论,假定数据传输只在一个方向进行,即A发送数据,B接收并给出确认。

1. 以字节为单位的滑动窗口

● 滑动窗口的工作

TCP的滑动窗口是以字节为单位的。当A收到B发来的确认报文段,获取其中的窗口确认号时,A就可以构造出自己的发送窗口。如:收到的窗口是20字节,确认号是31。
构造发送窗口
在没有收到B的确认的情况下,A可以连续地将窗口内的所有数据全部发送出去。未收到确认的数据也要暂留在窗口中保存,以便于超时重传。在不考虑拥塞等因素的前提下,窗口越大,传输效率就会越高。
窗口后沿后面的部分表示已经发送并且收到了确认,而前沿前面的部分表示目前不不允许发送,因为接收方的接收窗口没有为其准备临时存放的缓存空间。
发送窗口的位置由前沿后沿决定。窗口的后沿只能前移或不变,因为不能撤销已经收到的确认;窗口的前沿一般只能也只能前移和不变。前沿虽然可以收缩,但TCP标准强烈不赞成收缩,因为窗口收缩可能会造成错误(已发送的分组被收缩)。
前沿和后沿并不总是一起移动的。可能后沿前移,但窗口缩小了,前沿不变;也可能后沿不变,窗口增大了,前沿前移。
状态1
设发送窗口有三个指针p1,p2和p3。p1是后沿、p3是前沿、p2是已发送但未收到确认的最后一个分组序号。一个发送窗口可以由这三个指针表示:
p3 - p1 = A的发送窗口;p2 - p1 = 已发送但未接收确认的部分;p3 - p2 = 允许发送但未发送的部分(可用窗口)

而在B的接收窗口中,能够接收31~50这20个字节的数据。当收到后沿所在字节的数据时,B可以发送新的确认报文段,更新确认号;如果数据未按序到达(即收到的数据不是后沿所在字节的数据),就不能更新确认号,而是再发送确认号为31的报文。
状态2
现在B收到了31、32、33这三个分组,根据累积确认,只需要发送确认号为34的确认报文段。A收到确认报文后,将发送窗口的后沿向前移动;并且由于窗口没有变化,前沿也向前移动。因此,A的发送窗口中,可用窗口扩大了,可发送的数据也变多了。
当滑动窗口的可用窗口为0时,A不能再发送任何分组,必须等待B回送确认报文段。

● 窗口与缓存

TCP连接的双方都有一个缓存。发送方的应用进程将数据写入缓存等待发送,而接收方从缓存中取出数据交付给应用进程。
窗口与缓存
发送缓存用于存放已发送但未收到确认的数据、以及应用进程准备好发送的数据
发送窗口是发送缓存的一部分,其后沿和发送缓存重合(已确认的部分可以从缓存中删除),而前沿小于等于发送缓存中最后的字节。前沿到缓存尾的部分就是不允许发送的部分。由于缓存是有限的,应用进程必须控制写入缓存的速度不能过快,否则会没有存放数据的空间。
接收缓存用于存放已收到但未读取的数据、以及未按序到达的数据
接收窗口不能看作接收缓存的一部分,因为接收缓存只存有已到达的数据;而接收窗口需要维护未到达数据的空间。
如果应用进程不能及时读取数据,就会导致接收窗口逐渐变小,直至为0,表示接收端无法再接收新的分组。反之,如果进程能及时读取数据,甚至速度快于接收的速度,则接收窗口可以不变或增大,直至等于整个接收缓存的大小。

● 窗口的注意点
  1. 虽然发送方的发送窗口是由接收方的接收窗口设置的,但同一时刻不保证它们两者一样大。因为确认分组需要一定时间的滞后才能到达发送方,而该过程中,接收窗口可能发生改变。
  2. 对于不按序到达的数据如何处理并没有明确规定。最简单的做法是直接丢弃,但浪费网络资源。通常是临时存放在接收窗口中,待缺少的数据到达后,按序交付给应用进程。
  3. 接收方必须有累积确认的功能。接收方可以选择合适的时候发送确认,或在自己发送分组时捎带确认信息。但是确认推迟的时间不应过大,TCP标准规定的时间不超过0.5秒。
  4. TCP是全双工通信,每一方都有发送窗口和接收窗口,分析时务必分清。

2. 超时重传时间的选择

● 计算公式

重传时间的确定是TCP协议最复杂的问题之一
TCP采用自适应算法

  1. 记录报文发出的时间与收到确认的时间,并由此计算得到报文段往返时间RTT
    TCP会保留RTT的一个加权平均往返时间RTTs(称平滑往返时间),第一次得到RTT时,RTTs = RTT;此后每次得到一个新的RTT值时,采用下面的公式更新RTTs:
    RTTs(新) = (1 - α) × RTTs(旧) + α × RTT (0 < α < 1)
    若α -> 0,则新旧的RTTs相差不大,也就是受RTT的影响不大,更新的慢;若α -> 1,则新的RTTs受RTT影响大,更新的快。
    RFC-6298推荐的α值为0.125
  2. RTTD是RTT偏差的加权平均值,与RTTs和新RTT样本有关。第一次得到RTT时,RTTD取RTT的一半;此后每次得到一个新的RTT值时,采用下面的公式更新RTTD
    RTTD(新) = (1 - β) × RTTD(旧) + β × |RTTs - RTT| (0 < β < 1)
    β的推荐值为0.25
  3. 超时重传时间RTO的设置应略大于RTTs,RFC-6298推荐下式计算RTO:
    RTO = RTTs + 4 × RTTD
● Karn算法

在TCP传输中,发送方如果重传了分组,则最后可能会收到多个确认。如何确定分组对应的是哪次重传就无法得到正确的判断。而错误的判断,都会导致RTT偏大或偏小,导致导致最后计算的RTTs不准确,影响RTO的有效性。
Karn提出了一个算法:在计算RTTs时,只要是重传的报文段,就不采用其往返时间样本。这样避免了错误判断带来的误差。

然而Karn算法带来了一个新问题:如果由于网络状况的变化,报文段的时延突然增加了很多,以至于之后每一次发送的报文段都超时了。此时如果根据Karn算法,RTTs和RTO就永远无法更新。
因此,Karn算法进行如下修正:报文每重传一次,就将RTO增大一些;当不再重传时,才根据上面的公式计算RTTs和RTO
典型的做法是将新的RTO取为旧RTO的两倍

修正后的Karn算法能够区分有效和无效的样本,改进了往返时间的估测,使得到的RTO更合理且有效。

3. 选择确认SACK

选择确认是一种重传缺少数据的处理办法。
如果接收方接收到的数据只是未按序到达,缺少一些中间序号的数据,就可以使用选择确认让发送方只发送缺少的数据。
假设接收方接收了一些不连续的字节块并保留在接收窗口中,每两个不连续的相邻字节块都有两个边界:左边界和右边界每一个数据块的范围就是左边界到右边界-1。如果给出这些信息,就能让发送方知道需要重传的分组和已经正确接收的分组,通知发送方不必重传已收到的数据。
不连续字节块的边界
TCP首部的固定部分没有字段提供这些边界信息。要传送这些信息,必须使用选择确认SACK字段,这是一个选项字段。
在使用选择确认字段建立TCP连接时,首部必须加上允许SACK的选项,双方必须事先商定好。
使用选择确认字段时,确认号字段的用法不变,只是在首部增加了SACK选项,以报告收到的字节块的边界。
SACK选项字段需要1字节指明是SACK选项,以及1字节指明选项占用的字节数。剩余用来表示边界的长度最多只有38字节。每一个边界需要4字节,因此最多指明9字节,但指明一个字节块需要2字节。综上,SACK最多可以指明4个字节块的边界信息。

由于SACK文档没有指明如何相应SACK,因此大多数连接仍采用原来的办法,即重传所有未确认的数据块。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章