美文网首页
TCP(III) 流量控制

TCP(III) 流量控制

作者: 小小小超子 | 来源:发表于2018-03-26 16:06 被阅读58次

    TCP 数据流与窗口管理

    交互式通信

    ssh 是一个典型的交互式通信协议,它是加密了的,通常每次按键都会生成一个单独的包。另外,ssh 会在服务端对客户端输入的字符进行回显。因此,服务端收到数据包会先发送一个 ACK,然后发送一个回显报文,再由客户端发送对回显报文段的 ACK,如下左图。

    image

    但通常第二段和第三段可以合并来减少传输次数,这种方法称为捎带延时确认,如上右图。我们可以再来看看 wireshark 抓的包:

    image

    不难发现,这些包三个为一组,先是客户端(192.168.0.120)发送一个加密的包,然后收到服务端一个加密包,然后客户端回复一个 ACK。看一下服务端发过来的包:

    image

    可以看到,标志位的 ACK 置为了 1,ACK 值为 145,PSH 位也置为了 1(表明收到后立刻返回给应用层),并且数据段不为空,说明包含了回显的部分。很明显这里采用了捎带延时确认。

    延时确认

    在很多情况下,TCP 累计确认可以允许延迟一小会儿发送 ACK,以便结合相同方向的数据一起传送。但是显然,TCP 不能延时任意时长,通常建议:TCP 实现延迟应小于 500 ms,实践中延时应小于 200 ms。该延时值是可以配置的可选值如下:禁用延时,始终延时,每隔一个包回复一个 ACK,自动确认时间;默认值为 3。

    Nagle 算法

    通常在类似于 ssh 这样的应用里,每次传输的数据包非常小,称为微型报文,这些报文会造成相当高的传输代价。可以采用 Nagle 算法来解决这类问题。

    Nagle 算法规定,当一个 TCP 连接有在传数据时,小的报文段(长度小于 SMSS)不会被发送,直到所有在传数据都收到 ACK。并且收到 ACK 后,会收集小数据并整合到一个报文段发送。

    下图为 ssh 应用中没有开启 Nagle 算法(左图)和开启 Nagle 算法(右图)的差别。

    <img src="http://image.littlechao.top/20180317121248000003.jpg" height="400px" >

    可以看到,没有开启时,同一时刻有很多包在传输,并且有很多小包(tinygram),总体时间短,但是网络负担大。开启了 Nagle 算法时,同一时刻只有一个方向的传输,并且在合适的情况下,合并小包一起发送,时间较长,但传输次数少,网络负担小。

    延时 ACK 与 Nagle 结合

    无论是延迟 ACK 还是 Nagle 算法,其目的都是减少网络中传输的包,减轻网络负担。但是二者一起使用时,可能会出现问题。

    考虑如下情况:客户端依次要发送一个全长报文段以及若干个小包,服务端收到第一个全长包,然后延迟发送 ACK(延时,或者期待第二个全长报文段到达),而客户端采用了 Nagle 算法,需要等到 ACK 到达才能继续发送。这样就会形成一个短暂的死锁,性能反而变差。所以在有些情况下,如 ssh ,可以禁用 Nagle 算法。

    流量控制与窗口管理

    每个 TCP 报文段的首部里都包含了一个窗口大小字段,该字段占 16 位,最大表示 65536,也就是 64 KB,但 TCP 选项中的窗口缩放选项可以让它表示更大的窗口。

    一般来说每个连接的接收端会有一个大小固定的缓存,用来暂存发送端发来的数据,然后由应用程序读取。当应用程序来不及处理缓存数据,而发送方又不停的发送数据,超出缓存大小,就会造成数据丢失和不必要的重传。窗口大小字段用于 TCP 的流量控制,用于表示接收端可用缓存大小。

    滑动窗口

    发送窗口

    每个 TCP 连接的两端都维护了一个发送窗口,结构如下:

    <img src="http://www.tcpipguide.com/free/diagrams/tcpswwindows.png">

    主要分四部分:已经发送并收到确认、已经发送但未收到确认、未发送但可以发送、未发送并且目前不能发送。第三部分称为可用窗口,第二部分和第三部分合称发送窗口。随着时间推移,窗口可以有几种运动:

    • 关闭:随着 ACK 到来,发送窗口左边界向右移,窗口减小。
    • 打开:窗口右边界右移,即接收方可用缓存增大,发送方可用窗口也就增大。
    • 收缩:右边界左移,主机不支持这种做法,但 TCP 必须能处理这种问题。
    接收窗口

    除了发送窗口,接收方还维护了一个接收窗口。

    image

    接收窗口结构简单,包括已经接收且已经回复 ACK、允许对方发送但还未收到、目前不允许对方发送的部分。

    控制流程

    接收方收到一个数据包后,返回一个 ACK,根据自己的可用接收缓存大小设置 ACK 报文里的窗口大小。发送方收到这个窗口通告后,根据可用窗口大小调整自己的发送窗口,以达到调节发送速率的目的。

    零窗口与 TCP 持续计时器

    当发送方不停发送,接收方又比较忙的时候,可能会导致可用窗口大小为 0。当出现零窗口时,发送方收到一个 ACK 报文,其中的窗口大小为零,那么就表示暂时不能发送数据。而当接收方应用程序开始处理收到的数据,使得接收缓存里又有了空间,但是我们知道,窗口通告是包含在 ACK 报文里的,没有收到新的数据,就没法发送窗口通告,这时就会造成死锁。

    所以当出现零窗口时,发送方会采用一个持续计时器间歇地查询接收端是否有可用窗口,持续计时器会触发窗口探测(window probe)的传输。为了保证对方能够收到查询,我们必须要往数据段放一些数据,以保证超时重发,通常会放一个字节的数据 。接收方收到窗口探测后,会被强制返回一个 ACK,并且包含自己当前可用窗口大小(这个大小会有特殊情况)。注意这里采用指数退避来计算持续计时器的超时时间。

    前面谈到,大量数据量较小的包会造成传输速度的下降,也会造成网络负担。当接收端可用缓存从 0 慢慢增大到一个较小的值,这时候收到一个窗口探测,为了避免发送较小的包,接收端可以仍然在 ACK 中回复一个零窗口。

    image

    糊涂窗口综合征

    当发送方发送的报文大小不固定时,可能会出现糊涂窗口综合征(Silly Window Syndrome,SWS)。当出现该问题时,交换的报文数据段大小较小,耗费的资源较多。

    TCP 连接两端都可能导致 SWS:当接收端的通告窗口较小(或者是还没等到窗口变得够大),或者发送端发送的包较小(或者是没有等待其他小数据组合成大数据包)。为了避免 SWS,发送方要遵循一定规则。

    对于接收方:

    • 不应通告小的窗口值。在窗口增至 min(MSS,接收端缓存的一半) 之前,不能通告比当前窗口(可能为 0)更大的值

    对于发送方,满足任意一个:

    • 全长报文段可以发送
    • 数据段长度 >= 最大窗口通告的一半
    • 该连接禁用 Nagle 算法
    • 没有未经确认的在传数据

    大容量缓存与自动调优

    在相似的环境下,较小的接收缓存的 TCP 应用吞吐性能会较低。很多 TCP 协议栈中上层应用不能指定接收缓存大小,由操作系统来指定一个固定的或者动态变化的值。

    相关文章

      网友评论

          本文标题:TCP(III) 流量控制

          本文链接:https://www.haomeiwen.com/subject/pxprcftx.html