网络编程基础(4)-协议概要-TCP的流量控制

序列号和确认应答

这里,我们先回头来看看在TCP首部中简要介绍过的序列号和确认应答号。

序列号是按顺序给发送数据的每一个字节都标上号码的编号。序列号用来解决网络包乱序的问题。
这里写图片描述

确认应答号是接收端查询接收数据TCP首部中的序列号和数据长度(IP首部中的数据包长度-IP首部长度-TCP首部长度),将自己下一步应该接收的序列号作为确认应答号返送回去。用其来告知是否有丢包的情况发生。
这里写图片描述

交互数据流

TCP通信数据,大致可以分为两类,一类是交互数据流,以客户端和服务器相互发送交互数据为主,报文长度一般都比较小,两端皆有数据流动。另一类是类似文件传输服务的成块数据流,报文段基本都是满长度。数据流向基本是服务器到客户端单向居多。对于这两类数据,TCP有不同的处理算法。

延迟确认应答

TCP协议规定在接收到数据段时需要向对方发送一个确认,但如果只是单纯的立即发送一个确认,会降低网络利用率,而且可能会返回一个较小的窗口(引起糊涂窗口综合症,后文讲解)。所以TCP在何时发送ack给对方有以下规定:

1:当有响应数据要发送时,ack会随响数据立即发送给对方,这也是常说的数据捎带ACK。

2:如果没有响应数据,ack的发送将会有一个延迟,以等待看是否有响应数据可以一起发送。但这个延迟最多不会超过500ms(否则可能引起对端重传),一般为200ms。如果在200ms内有数据要发送,那么ack会随数据一起立即发送给对方。这里的延迟200ms,不是指的从接收到对方数据到发送ack的最长等待时间差。而是指的内核启动的一个定时器,它每隔200ms就查看下是否有ack要发送。例如:假设定时器在0ms时启动,对方的数据段在185ms时到达,那么ack最迟会在200ms时发送,而不是385ms时发送。

3:如果在等待发送ack期间,对方的第二个数据段又到达了,这时要立即发送ack。但是如果对方的三个数据段相继到达,那么第二个数据段到达时ack立即发送,但第三个数据段到达时是否立即发送,则取决于上面两条。

交互数据流总是以小于最大报文段长度的分组发送。对于这些小报文段,接收方延迟确认应答来判断是否可被推迟发送,以便与回送数据一起发送。这样通常会减少报文段数目。

Nagle算法

广域网上传输大量小分组(用户数据很少的报文)时,会减少网络利用率,还可能会增加拥塞的概率。Nagle算法是针对此问题的解决办法。

具体来说该算法是一种延迟发送数据的处理机制,当下列任意条件达成时才可发送数据。否则,暂时等待一段时间(在这段时间内收集合并这些小分组)后再进行数据传送。
条件1:已发送的数据都已经收到确认应答。
条件2:可以发送最大段长度的数据时。

该算法是自适应的,确认到达的越快,数据也就发送得越快。虽然可以提高网络利用率,但是和延迟确认应答相互作用可能会引发某种程度的延迟。一般局域网很少使用这个算法,在窗口和机械控制领域中使用TCP,往往会关闭该算法。

成块数据流

前面说到Nagle算法通常用在较慢的广域网中减少小分组数目。但是对于像文件传输这种大多数都是达到MSS长度的分组,如果发送一个段,停止等待一个确认,显然会降低传输效率。对此,TCP使用滑动窗口协议,它允许发送方在等待一个确认前可以连续发送多个分组,只要这些连续的分组总长度不大于接收方告知的窗口大小。

滑动窗口协议

滑动窗口实例

TCP首部中有一个字段叫做窗口大小。这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。下图是一个TCP通信实例图(出自《TCPv1:协议》):

这里写图片描述

报文段1、2、3对应TCP三次握手连接过程,连接双方相互告知MSS,窗口大小信息。
发送方首先传送4、5、6三个数据报。从报文段7的ack值2049可以看出仅确认了4、5两个报文段,随后报文段8确认了报文段6。这一过程可以对照前面延迟确认应答的第3点理解。报文段8中的窗口大小为3072,这说明,TCP的接收缓存中还有1024个字节数据等待被应用程序读取。
报文段11-16说明通常会使用“隔一个报文段确认”的策略。如报文段14确认报文段11和12,报文段13确认报文段13和15。这是因为使用滑动窗口协议时,接收方不必确认每一个收到的分组,在TCP中,ack是累积的,表示接收方已经正确收到了确认应答号减1的所有字节。
报文段17-20对应TCP四次挥手断开连接。

这里注意一下上图中的PSH。表明此报文段是设置了PUSH标志位的。该标志位表明发送方通知接收方将所有收到的数据全部提交给应用进程。这里的数据包括与PUSH一起传送的数据及接收方TCP已经为接收进程收到的其他数据。大多数TCP的实现能够自行决定何时设置这个标志。

现在用可视化的方法展示滑动窗口协议。
这里写图片描述
上图中,将字节从1到11编号,接收方通告的窗口称为提供的窗口,覆盖第4到第9的区域,表明接收方已确认了第4字节之前的数据,且通告的窗口大小为6。发送计算可用窗口,该窗口表明多少数据可以立即发送。
当接收方确认数据后,这个窗口不时的向右移动。窗口的两个边沿的相对运动增大或减小了窗口大小。
1:窗口左边沿向右边靠近,称为窗口合拢。数据被发送和确认时发生该现象。
2:窗口右边沿向右边移动,称为窗口张开。允许发送更多的数据,当另一端的读取已经确认的数据并释放TCP的接收缓存时发生该现象。
3:窗口右边沿向左移动,称为窗口收缩。RFC强烈建议不使用这种方式。

下图展示了上面通信实例图数据传输过程中滑动窗口协议的动态性。
这里写图片描述

窗口大小的确定

无论如何窗口大小不可能是一个随机数,它有自己的计算准则,其中一个计算规则如下图所示:
这里写图片描述
这也称为带宽时延乘积。
根据上图公式所计算出的窗口大小为:
WS=10,000,000x0.01=>WS=100,000bitsor(100,000/8)/1024=12,5Kbytes
按照这种方式计算出的窗口大小理论上(慢速广域网上带宽是共享而非独占,所以这是在理论上的)是最有效率的,因为发送端可以连续发送多个段来填满这个管道,形成最大吞吐量。

窗口扩大选项

上图计算的窗口大小为125Kb,而TCP首部中窗口大小字段为16位,最大表示64Kb的窗口。那如何表示125Kb窗口呢?TCP首部选项中有个窗口扩大选项,使用该选项可以使得发送端得到更大的通告窗口,这样就可以发送更多的数据,提高数据传输效率。

窗口扩大选项占3字节(请结合TCP首部一节),其中有一个字节表示移位值S。新的窗口值等于TCP首部中的窗口位数以16增大到(16+S)。移位值允许使用的最大值是14,相当于窗口最大值增大到2^(16+14)-1 = 2^30 - 1。

该选项只能出现在SYN报文中,因此当连接建立起来后,每个方向上的扩大因子是固定的。主动建立连接的一方在其SYN中发送这个选项,而被动建立连接的一方只能够在收到带有这个选项的SYN后才可以发送这个选项。每个方向上的扩大因子可以不同。但是被动连接方如果回复报文段中没有这个选项,那么这个连接将不使用窗口扩大,这是为了向后兼容,因为较老的系统可能不支持该选项。

坚持定时器

TCP通过滑动窗口进行流量控制,如果窗口为0,这会一种什么情况。下图展示了一个从快的发送方和慢的接收方的通信实例。
这里写图片描述
发送方连续发送4-7四个报文段去填充对端的窗口,然后等待ACK。报文段8表示接收方发送ACK,但是通告窗口却为0,说明接收方已经收到所有数据,但是这些数据还在TCP缓冲区。发送方得知接收方窗口为0,它将不发送任何数据。少许时间后,接收方发送报文段9,窗口大小4096。此报文段并不确认数据,只是通告窗口大小,因此被称为窗口更新。随后获知窗口大小的发送方打开窗口并继续发送数据…

然而实际情况中,窗口更新(报文段9)是有可能丢失的。如果丢失,那发送方会一直傻等吗?并不会。发送方会使用一个坚持定时器来周期性地向接收方发送1个字节的窗口探测报文,以便发现窗口是否已经增大。TCP从不放弃发送窗口探测,这个过程持续到窗口打开或者连接被终止。
在上图中,发送方接收到报文段8,得知窗口为0从而设置坚持定时器,在坚持定时器溢出前,接收方发来了窗口更新,于是发送方继续传输数据。但如果在坚持定时器溢出前,发送方并没有收到窗口更新,那么它将发送一个1字节窗口探测报文并再次设置坚持定时器。

糊涂窗口整合症

避免措施

基于窗口的流量控制可能会出现“糊涂窗口整合症”的情况。接收方通告一个小窗口(而不是一直等到有大窗口时才通告),而发送方可以发送少量数据(而不是等待其它的数据以便一次发送一个大的报文段)。这样的情况就会造成传输的分组增多,降低网络利用率。可以采取以下措施避免。
1:如果这个问题由接收方引起,那么接收方不通告小窗口。只有当缓冲区增加不小于MSS大小或增加不小于接收缓存一半大小的时候,它才发送一个非0窗口通告,否则回送一个0窗口通告。
2:如果这个问题由发送方引起,那么就使用Nagle算法。

实例

我们来看一个避免出现糊涂窗口的实例,该例子也包含了坚持定时器和延迟确认应答。描述该例需要两个图,一个是时间系列图:
这里写图片描述
一个是事件序列图:
这里写图片描述

我们对照两图来描述。
发送方发送1-4报文段去填充满接收方的缓冲区。接收方回复报文段5,确认数据并通知一个0窗口。发送方此时设置坚持定时器,大概5秒后坚持定时器溢出,此时发送一个1字节的窗口探测(报文段6),该字节被接收并确认(报文段7)了。原因是在3.99时刻应用进程已经从TCP缓冲区中读取了256字节,使得有了可用的接收缓冲区。然而报文段7却通告了0窗口,这又是因为仍然没有足够的空间来接收一个满长度的报文,或者不能腾出缓存空间的一半。这就是接收方的糊涂窗口避免措施。
发送方的坚持定时器被复位,并在5秒后再次溢出(在时刻10.151)。然后又发送一个字节并被确认(报文段8和9),而接收方的缓存空间还不够用(1022字节,小于一个MSS),使得通告窗口为0。
发送方的坚持定时器在时刻15.151再次溢出,于是又发送了另一个字节并被确认(报文段10和11)。这一次由于接收方有1533字节的有效缓存空间,因此通告了一个非0窗口。发送方立即利用这个窗口发送了1024字节的数据(报文段12)。对这1024字节数据的确认(报文段13)通告其窗口为509字节。前面说过发送方应避免通告小窗口,这里为何可以发送一个小于MSS的窗口?如果这里按照接收方避免糊涂窗口的措施而发送一个0窗口的话,那会使窗口右边沿向左移动而造成窗口收缩,前面说过,RFC强烈建议不使用窗口收缩。
接下来我们看到发送方没有立即向这个小窗口发送数据。这就是发送方采取的糊涂窗口避免策略。相反,它等待坚持定时器在时刻20.151溢出,并在该时刻发送509字节的数据。尽管它最终还是发送了一个长度为509字节的小数据段,但在发送前它等待了5秒钟,看是否会有一个ACK到达,以便可以将窗口开得更大。这509字节的数据使得接收缓存仅剩下768字节的有效空间,因此接收方通告窗口为0(报文段15)。
坚持定时器在时刻25.151再次溢出,发送方发送1个字节,于是接收缓存中有1279字节的可用空间,这就是在报文段17所通告的窗口大小。
发送方只有另外的511个字节的数据需要发送,因此在收到1279的窗口通告后立刻发送了这些数据(报文段18)。这个报文段也带有FIN标志。接收方确认数据和FIN,并通告窗口大小为767字节(因为FIN要占用一个字节,所以不是768而是767)。
接收应用进程继续每隔2秒从接收缓存区中读取256个字节的数据。为什么在时刻39.99发送ACK(报文段20)呢?这是因为应用进程在时刻39.99读取数据时,接收缓存中的可用空间已经从原来通告的767(报文段19)变为2816,这相当于接收缓存中增加了额外的2049字节的空间。因为现在接收缓存已经增加了其空间的一半,因此接收方现在发送窗口更新。这意味着每次当应用进程从TCP的接收缓存中读取数据时,接收方的TCP将检查是否需要更新发送窗口。
注意到报文段5、7、9、11、15、17的发送时刻为:0.170、5.170、10.170、15.170、20.170、25.17,这是因为接收方的延迟确认应答机制造成的。

紧急方式

TCP提供“紧急方式”,它使一端可以告诉另一端有些具有某种方式的紧急数据已经放置在普通数据的数据流中。具体的做法是:将TCP首部中URG标志位置1,并且将16位的紧急指针置为一个正向偏移量,该偏移量与TCP首部中的序号字段值相加,得出紧急数据的最后一个字节的序号。紧急数据的表示下图:

这里写图片描述

TCP只通告紧急方式已经开始并指明紧急数据最后一个字节的指针,至于对数据的处理留给应用层程序去做。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值