流量控制
发送方不能无脑的发送数据给接收方,要考虑接收方的处理能力。
如果一直无脑的发送数据给对方,但是对方处理不过来,那么就会触发重传机制,从而导致网络流量无端的浪费。
为了解决这种现象发生,TCP提供一种机制可以让【发送方】根据【接收方】的实际接收能力来控制发送的数据量,这就是所谓的流量控制。
操作系统缓冲区与滑动窗口的关系
我们假定发送窗口和接收窗口是不变的,但实际上,发送窗口和接收窗口中所存放的字节数,都是放在操作系统内存缓冲区的,而操作系统缓冲区,会被操作系统调整。
当应用进程没办法及时读取缓冲区的内容时,也会对我们的缓冲区造成影响。
为了防止这种情况的发生,TCP规定是不允许同时减少缓存又收缩窗口的,而是采用先收缩窗口,过段时间再减少内存,这样就可以避免丢包情况。
窗口关闭
在前面我们都看到了,TCP通过让接收方指明希望从发送方接收的数据大小(窗口大小)来进行流量控制。
如果窗口大小为0时,就会阻止发送方给接收方传递数据,直到窗口变为非0为止,这就是窗口关闭。
窗口关闭的潜在危险
接收方向发送方通告窗口大小时,是通过ACK报文来通告的。
那么,当发送窗口关闭时,接收方处理完数据后,会向发送方通告一个非0的ACK报文,如果这个通告窗口的ACK报文在网络中丢失了,那就麻烦大了。
这会导致发送方一直等待接收方的非0窗口通知,接受方也一直等待发送方的数据,如不采取措施,这种互相等待的过程,就会造成死锁的现象。
TCP是如何解决窗口关闭时,潜在的死锁现象呢?
为了解决这个问题,TCP为每个连接设有一个定时器,只要TCP连接一方收到对方的零窗口通知,就启动持续计时器。
如果持续计时器超时,就会发送窗口探测(Windos probe)报文,而对方在确认这个探测报文时,给出自己现在的接收窗口大小。
窗口探查探测的次数一般为3次,每次大约30~60秒(不同的实现可能会不一样)。如果超过3次之后接收窗口还是0的话,有的TCP实现就会发送RST报文来中断连接。
糊涂窗口综合症
如果接收方太忙,来不及取走接收窗口里的数据,那么就会导致发送方的发送窗口越来越小。
到最后,如果接收方腾出几个字节并告诉发送方现在有几个字节的窗口,而发送方会义无反顾地发送这几个字节,这就是糊涂窗口综合症。
怎么让接收方不通告小窗口呢?
接收方通常的策略如下:
当【窗口大小】小于 min(MSS,缓存空间/2),也就是小于 MSS 与 1/2 缓存大小中的最小值,就会向发送方通告窗口为0,也就阻止了发送方再发送数据过来。
等到接收方处理了一些数据后,窗口大小 >= MSS ,或者接收方缓存空间有一般可以使用,就可以把窗口打开让发送方发送数据过来。
怎么让发送方避免发送小数据呢?
MSS(maximum segment size)最大报文段长:通常根据最初的最大链路层帧长度来设置
MTU(maximum transmission unit)最大传输单元:本地发送主机发送长度是MSS这样的帧
发送方通常的策略:
使用 Nagle 算法,该算法的思路是延时处理,它会满足以下两个条件中的一条才可以发送数据:
- 要等到窗口大小 >= MSS 或是 数据大小>= MSS(最大报文段长 maximum segment size)
- 收到之前发送的数据的 ACK 回包
只要没满足上面条件中的一条,发送方一直在囤积数据,直到满足上面的发送条件。
另外 Nagle 算法默认是打开的,如果对于一些需要小数据包交互的场景的程序,比如 telnet 或 ssh 这样的交互性比较强的程序,则需要关闭 Nagle算法。
可以在Socket 设置 TCP_NODELAY 选项来关闭这个算法(关闭 Nagle 算法没有全局参数,需要根据每个应用自己的特点来关闭)