TCP流量控制
引言
因为接收端的缓冲区对于发送端来说是非常重要的 如果发送端不清楚接收端大小 只管本端的数据的话 就有可能导致接收端缓冲区溢出 这样就引入了流量控制 通过动态调节窗口大小来控制发送端行为 窗口大小在一定情况下还会影响拥塞问题
延时确认机制
采用传送应答机制虽好 可以保证可靠性 但如果一个数据报对应一个空的ACK 那么会使得网络中的小包较多 根据包守恒原则 我们可发的数据包就相应的变少了 所以在发送ACK的一方 如果能够将一个ACK与一个数据包放在一起发送 这样不就在完成功能的情况下大大减少了包的数量吗 所以我们引入了延时确认机制
但我们也很容易想到一个问题 就是如果将一个ACK和一个数据包放到一起发送 一般来说我们肯定先确定要发送一个ACK 再确定要发送一个数据包 然后决定把两个包合在一起 那么ACK要等待多长时间呢 如果较短 那么有可能起不到效果 如果较长 可能会出现不必要的超时重传(这通常会引发大动作TCP拥塞控制) 一般来说时延应该小于500ms,但一般在实践中最大取200ms
Nagle算法
我们可以想象一种网络传输的情况 就是一种交互式的场景 数据传输双方要求准确快速的交换数据 比如按键 短消息等 这样会导致一段时间内网络中出现大量的小数据包,这个小数据包意味着其数据相对于数据包头部来说很小 我们极端一些 假设IP和TCP的头部都是20个字节 而数据只有一个字节 那么其实这个数据包除了保证它的可靠性以外其传输效率极低 因为实际吞吐量等于单位时间内传输数据的大小 上面所说的情况就会使得网络中的吞吐率降低 局域网不谈 在广域网上则有可能会加重拥塞现象 严重影响网络性能 这里提出了一种方法 即Nagle算法
Nagle算法是为了减少网络中的小包数量 提高网络利用率 其基本原理也很简单 即在网络中有在传数据时 小于一个SMSS(发送端最大报文段)的报文就不会发送 而是等到小数据整合成一个大数据的时候再发送 且Nagle算法遵循停等规程(stop-and-wait) 即收到全部的在传数据的ACK才能发送数据 这意味着Angle算法实现了自时钟控制(self-clocking) 即发包速率依据RTT ACK越快 发送越快 即"RTT控制着发包速率"
但Nagle算法也不是在所有情况下都会提升效率 有一种特殊情况 使用Nagle算法可能使得程序巨慢无比 即延迟ACK于Nagle算法的死锁问题
因为遵循停等原则 那么有可能接收端Nagle等待ACK 发送端延迟确认 即造成死锁 造成无谓的时延(Nagle因为停等规程本来时延就高于一般无Nagle的传输)
滑动窗口
说到流量控制我们一定不得不提滑动窗口,这当然不是一个真的窗口,而是在逻辑很像一个向右移动的窗口所以得名滑动窗口
首先我们要明确一个问题 在每个链接中我们都会在发送方与接收端维护两个窗口结构 发送窗口和接收窗口 我们首先来看看这个结构
(图片来源:传送门)
如图上所说 我们把一个窗口分成四部分 第一部分为发送并已经确认 即收到的ACK序列的最大值 第二部分为已发送但为接收到ACK的部分 第二部分也是滑动窗口的最左端 第三部分为我们已知可以发送的大小 不会造成接收端缓冲区溢出 也是一般不会造成拥塞的 第三部分为滑动窗口的右边 第四部分则是不能发送的部分
我们称第二部分与第三部分加起来为提供窗口(offered window) 我们知道TCP报文头部中有16位的窗口大小 最大表示65535字节的窗口 这也表示了接收端缓冲区现在可以接收的大小 你也许会质疑这也太小了 没错 它确实太小了 所以在TCP的选项部分引入了窗口缩放选项(WSPOT) 窗口缩放选项可以有效的使得窗口大小从16位扩展到30位 使得实际窗口最大为1073741823(2^30 - 1) 刚好1GB 这样就解决了我们所说的接收端缓冲区溢出问题 在每个报文段中都有一个窗口选项 标记了接收方的接收极限 也是发送方能发送的大小(不涉及拥塞控制的情况下) 我们再来看看窗口的缩放
当收到非重复ACK的时候 窗口第一部分右移 移动收到的ACK序列号减去接收前最大的ACK 第三部分右移 减去的大小+(新窗口大小-旧窗口大小) 这样就完成了一次窗口的"滑动",
糊涂窗口综合征与零窗口
窗口自动调优
我们首先需要了解一个公式
当前吞吐量 = RTT *当前窗口大小
理想吞吐量(bit) =路径带宽(bit/秒为单位)*往返时间( RTT )以秒为单位
那么该如何设置初始窗口大小呢 我们首先要明确一个问题 大的窗口会使得吞吐量上升 但是是过大的窗口会使得网络一次性进入大量的包 容易造成拥塞 而过小的包会浪费大量的吞吐量 因为实际吞吐量与窗口大小有关 所以如何满足呢 这就是我们所说的自动调优(慢启动改变的是拥塞窗口 但基本逻辑差不多)
即刚开始设置一个较小的窗口值 接收端每收到一个数据包 窗口大小增大2个MSS 这样可以使得在保持发送端最大发送速率的前提下 接收端和使用的缓存空间最小