TCP流量控制详解
滑动窗口
TCP 流量控制基于 滑动窗口 (连续 ARQ 协议) 实现。滑动窗口既保证了分组无差错、有序接收,也实现了流量控制。
发送方的滑动窗口由接收方控制,实现方法为:在接收方传递给发送方的报文中加入 Window Size 信息,从而告知发送方自己的窗口大小,且发送方必须服从接收方的管理 。在 TCP 实现中,两端的窗口大小相同 。另外需要注意的是,发送方不一定会一次性发送整个窗口的数据,这根据网络情况而定。
窗口本身是一种抽象 ,发送窗口有三个变量实现,分别为: (第一个未完成分组) (下一个待发送分组) (窗口大小)。
一般而言,流量控制分为两种:1)发送方输出的流量过多,接收方来不及接收。2)发送方发得太少,导致浪费带宽(数据只有一两个字节,而报头就占了几十个字节)。对于第一种则很好控制,只要发送方将每次输出的流量控制在接收方告知的窗口大小内即可;对于第二种情况(被称之为 糊涂窗口综合征 ),则相对复杂。
糊涂窗口综合征 :当发送端进程产生数据缓慢或接收端进程数据拉取缓慢时,会导致发送方以很小的段发送,这会降低信道的利用率。这个问题称之为糊涂窗口综合征。此问题可以从发送方和接收方解决:
- 发送方: Nagle 算法,Cork 算法
- 接收方:Clark Solution,延迟确认
延迟确认
延迟确认(Delayed Acknowledgements)用于接收方 ,其工作原理:当包到达接收方时,不立即确认,而是等待一定时间再发送确认。等待一段时间有两个好处:
- 此时间内接收方进程可以拉取数据,清空部分缓冲区以提供更大窗口(这样就可以使发送方一次发送更多的字节)。
- 接收方不必确认每一个段,减少了 pure ACK ,提高了通信效率。这基于发送方的 累积确认 。
需要注意,延迟确认最多延迟一次,即每两个报文就必须发送 ACK 报文;最多延迟 0.5 秒,若未等到下一个包,则直接发送 ACK 。同时,延迟确认必须同时满足以下几点才可使用 :
-
当前已经收到的但是还没有回复 ACK 的报文长度小于接收 MSS。
这很好理解,TCP 是基于字节流的协议 ,数据没有边界,所以发送方填充数据时应该会将每个段填充到最大长度( MSS )时才会继续填充下一个。若数据长度未达 MSS,我们就可以合理猜测对方数据已经发送完(也可能是因为对方进程产生数据太慢)。
-
当前没有处于 quick ACK模式
-
当前接收窗口中没有先前接收的乱序报文
仅有延迟确认机制,不会导致请求延迟(初以为是必须等到 ACK 包发出去,recv系统调用才会返回)。一般来说,只有当该机制与Nagle算法或拥塞控制(慢启动或拥塞避免)混合作用时,才可能会导致时耗增长。
quick ACK:
与延迟 ACK 对应,Linux 还有一个 quick ACK 模式,这种 quick ack 模式下就会对每个数据包都回复一个 ACK。在连接初始建立时候 、收包间隔大于 RTO 时 、收到不在接收窗内的报文的时候 等场景下就会进入 quck ack 模式,进入 quick ac k模式的时候,会把 quick ack 计数器初始化为 16 (也有可能是小于16的某个值),这就是说随后的 16 个数据包都不采用延迟 ACK。
Nagle 算法
如果发送方 TCP 正在为一个创建数据很缓慢的进程服务,则可能引起糊涂窗口综合征。解决方法是防止发送方一个一个字节地发送数据。发送方被迫等待到将数据集成较大的数据块时再发送。那么需要等待多久?太长,延误进程;太短,数据不够长,效率低。Nagle 提供了一个简单的方法解决此问题。
Nagle 算法用于发送方 ,其方法非常简单:
- 即使从发送方进程接收来的数据只有 1 字节,也直接将其封装并发送。
- 发送第一个段后,发送方在缓冲区积累数据并等待,直到发送方接收到 ACK 或数据积累到 MMS 或包中有 FIN 再发送。
Nagle与延迟ACK引发死锁
当 Nagle 算法和延迟 ACK 同时使能的时候,可能会造成如下情况:
- 客户端发送第一个小包,并被服务端接收。
- 客户端等待 ACK 或数据长度达到 MMS。
- 服务端延迟确认等待下一个包直到超时(0.5s)。
这使得两端相互等待,引发 死锁(deadlock) 。这种情况下就会提升平均时延,降低网络性能,因此可能需要禁用Nagle算法或者延迟ACK算法。另外,对于一些需要小包场景的程序——比如像telnet或ssh这样的交互性比较强的程序 ,你需要关闭这个算法。在 Socket 设置 TCP_NODELAY
选项来关闭这个算法
Cork 算法
Nagle 算法用于发送方 。Cork 算法与 Nagle 算法类似,但 Cork 算法则更为激进,一旦打开 Cork 算法,TCP 不关注是否有收到 ACK 报文,只要当前缓存中累积的数据量不满 MMS 时,就不会将数据包发出,直到一个 RTO 超时后才会把不满 MMS 的数据包发出去。
linux 中可以通过 TCP_CORK
选项来设置 Socket 打开 Cork 算法。TCP_NODELAY
选项和 TCP_CORK
选项在 Linux 早期版本是互斥的,但目前最新的 linux版本已经可以同时打开这两个选项了,但是 TCP_CORK
选项的优先级要比 TCP_NODELAY
选项的优先级要高。
CORK 与 Nagle 的区别是,Nagle 算法是尽量避免大量的小包发送,而 CORK 算法是期望完全避免发送小包(无论是大量还是少量的小包)。
Clark Solution
Clark Solution 用于接收方 。要避免接收方的糊涂窗口综合征,简单的办法就是不让接收方窗口每次只更新少量可用缓冲区,直到有较大空间时,才通知发送方窗口大小。Clark 解决方法是 只要有数据到达就发送确认,但宣布的窗口大小为零,直到窗口足够放入具有最大长度的报文段,或者至少半个缓存空间是空的 。