流量控制:点对点关系;发送方与接收方的通信, 主要是接收方能力较差时引起问题.
当发送速度 >接收的速度, 接收方就丢包. 丢包之后,发送方就会重传,而重传后的数据包又被丢弃.
拥塞控制:全局关系到整个网络; 主要是发送与接收端 之间的中间节点问题(路由器,交换机,集线器这些的问题);
网络拥堵,发送方无法收到ACK确认,就重传,而重传之后会使得网络再次拥堵.
1.流量控制部分
发送/接收缓存区:位于Socket Library 层
这里应用程序与发送/接收的缓冲区交互,而不是直接调用tcp/ip协议栈;
也就是非阻塞式编程.
- 应用程序调用write时,数据只是写入本机的发送缓冲区,其中并没有立即发到网络上.
- 应用程序调用read时,并不会等待网络上的数据到来,只是读取本地接收缓冲区的数据,读取不到就直接返回.
需要注意的是:
- 客户端/服务器之间的每条TCP连接上,都有一个发送缓冲区,一个接收缓冲区;也就是
TCP的连接数越多,消耗内存越多
; - 查看缓冲区大小:
net.ipv4.tcp_wmen = 4096,16384,4194304
; min(4K)、default(16K)、max. - 可设置发送/接收缓冲区的大,当然操作系统也可自动地调节.
1.1 滑动窗口
发送窗口
位于发送缓冲区,接收窗口
位于接收缓冲区
第一种案例:客户端写, 服务端读数据.
第二种案例:服务器写数据,客户端读数据.
发送窗口的结构
- 发送窗口: 即允许发出的数据范围,没有收到确认的ACK之前,允许发送的最大数据量范围;
- 发送窗口收缩到0后,不能再发送数据;
- 每次收到1次ACK,发送窗口就会向右滑动,P2变短,P3变长
接收窗口结构
- 接收窗口:即允许接收的数据范围.
- 数据每秒被应用程序接收读取后,该窗口就会向右滑动一次,然后删除窗口左边的部分.
- 若应用程序过慢,数据就不能被及时读取,最终堵塞窗口,将无法继续接收数据,收到新数据也会被丢弃.
1.2 流量控制
所谓流量控制
就是发送方与接收方动态调整自己的窗口大小范围
.
当窗口收缩到0时,可能出现死锁!!!
- 发送方的窗口收缩到0时,那么此时发送方无法和接收方进行数据通信,需要等待接收方发来
确认ACK
;确定新的发送窗口大小. - 若此时接收方发来的确认ACK
丢失
,那么发送方与接收方就会相互等待,出现死锁. - 如何解决该场景? --> 当发送方的窗口收缩到0之后,发送方就启动一个定时器,定期为接收方发送
探测报文
, 检测窗口是否扩大了,若检测时的窗口一直是0,那么就可以直接关闭连接.
1.3 发送方的延迟Nagle算法
IP网络层在设计时就是为了提升网络的吞吐量能力
,提升传输效率,所以不希望大量的极小分段数据包在网络中传输,耗费网络带宽速度.
小包:数据大小小于MSS的包;
极小包: 数据大小 小于(TCP头部+IP头部)
尽量避免出现过多的小数据包.
- 可让发送方延迟发出数据,根据Nagle算法 积累一批数据包后一次性发送.
- 可以让接收方延迟发出确认的ACK编号,积累一批后进行发送.
Nagle算法默认开启,当然也可以设置TCP的属性
TCP_NODELAY
来禁用它;
该延迟算法的核心原则为:任意时刻仅允许有一个未被ACK确认的小数据包存在.
允许发送的条件
- 若数据包 >= MSS[TCP的最大报文段长度],可以发送.
- 包含有FIN(关闭序列编号),允许发送;
- 设置了关闭Nagle算法的属性
TCP_NODELAY
,允许发送; - 发生超时[默认200ms],也允许发送.
注意: Nagle算法可能引发网络延迟,一般是禁用的,上层应用此时就可根据场景来控制数据的发送时机.
延迟ACK: 接收方收到一定量的数据包之后,再返回ACK确认.
普遍默认延迟200ms, 在接收方放一个定时器,定时查看200ms内是否有新的ACK需要发送,
而这个延迟时间实际小于等于
200ms, 案例:在160ms时收到了数据包,且接收处理完毕,那么再过40ms后即可返回该ACK;
2. 拥塞控制
注意:发送方只是猜测网络拥塞;而不是绝对确定.
当发送方发出一个数据包,长时间收到不到ACK
,那么可能是网络拥塞/接收方的接收能力问题.
由于这种不确定性,所以在发送方又设定了拥塞窗口, 反映网络的堵塞程度.
发送窗口 = Min(拥塞窗口/接收窗口)
发送方根据接收方回复ACK的快慢速度,可以动态地调节拥塞窗口的范围;也就是调节了发送窗口的范围.
案例:
- 开启,拥塞窗口较小,只要回复ACK的时候不超时,那么窗口就可以调大,收到一次ACK回复,就调大一次;
- 指数级增大 --> 线性增大.
- 当ACK超时,即可确认网络阻塞,可以将窗口缩小.
慢开始策略: 先进行指数级增长窗口大小.
拥塞避免: 由线性增长调整减小窗口大小.
对于网络拥塞的判断实际还要取决于ACK的超时时间,这个时间需要自适应调整.
那么按照惯性的思路,这个超时时间就按照RTT时间来设定; 超时时间大于RTT时间一点即可.
RTT时间:即
表示从发送端发送数据开始-->发送端收到来自接收端的确认
这个过程的往返时间
。
可是,RTT并不是一个固定的值,它是一个序列;
案例:
连续发数据包1,数据包2, 收到了ACK=2, 此过程时间为RTT1;
再发数据包3,收到ACK=3, 此过程时间为RTT2;
连续发数据包4,数据包5,数据包6,收到的ACK=6,此过程时间为RTT3.
要使用哪个RTT时间作为超时时间的标准呢?
可用时间序列预测中的EWMA[指数加权平均移动算法]
可以考虑使用历史数据序列值,预测目前的RTT值, 可调整超时时间;
RTTs(新) = (1-a) * RTTs(老) + a*RTT(最新值)
注:RTT:样本值,RTTs:预测值,a:常数[默认0.125]
快重传策略
快重传的设定:
(1)接收方要是收到失序的数据报文,就立即恢复ACK确认,不需要延迟ACK;
案例:接收方先收到数据包1和数据包2,回复ACK=3; 在此基础上,后面又收到数据包4和数据包5,但是没收到数据包3,即 数据包3已经丢包,那么就得发送ACK=3,即使ACK=3在上次已确认发送,这里仍然重复ACK回复.
(2)发送方连续收到三个重复的ACK,立即重传没有收到的数据报文,即使还没到超时时间.
未使用快恢复策略时;
采用快恢复策略
之后;
没有慢开始的指数级别增长,发生网络拥塞之后[即收到重复的ACK确认编号]后;从中间开始,线性增长.