所以说,通告窗口也是采用慢启动方式逐步张开的 。
2.0、收到极小载荷的TCP数据包时的慢启动
比如说收到了一个只包含1个字节载荷的数据包时,此时仅仅skb,协议头等开销就会超过几百字节,通告窗口增加是非常危险的 。Linux TCP实现中,将128字节定为下限,凡是收到小于128字节载荷的数据包,接收一大窗的数据非常有可能造成缓存溢出,因此不执行慢启动 。
2.1、收到满MSS的TCP数据包时的慢启动
如果能保证发送端一直发送满MSS长度的TCP数据包,那么接收缓存是不会溢出的,因为整个通告窗口可以使用的内存就是通过这个满MSS长度和接收缓存按照比例缩放而生成的,但是谁也不能保证发送端会一直发送满MSS长度的TCP数据包,所以就不能允许发送端一下子发送所有可用的窗口缓存那么大的数据量,因此慢启动是必须的 。
收到满MSS长度的数据或者大于MSS长度的数据,窗口可以毫无压力地增加2个MSS大小 。
2.2、收到非满MSS
这里的情况比较复杂了 。虽然收到数据长度比MSS小的TCP数据包有缓存溢出的风险,但是受限于当前的通告窗口上限(由于慢启动的功劳)小于整个可用的通告窗口内存,这种情况下即便是发送一整窗的数据,也不会造成整个接收缓存的溢出 。这就是说某些时候,当当前的接收窗口上限未达到整个可用的窗口缓存时,长度小于MSS的TCP数据包的额外高于 (n-1)/n比例的开销可以暂时“借用”剩余的窗口可用的缓存,只要不会造成溢出,管它是不是借用,都是可以接受的 。
如此复杂的情况,我画了一个稍微复杂点的图来展示,以节省文字篇幅:

文章插图
看懂了上图之后,我来补充一个动态过程,如果持续收到小包的情况下,会怎样?
如果持续收到小于MSS的小包,假设长度都相等,那么从慢启动开始,通告窗口的最大值,即rcv_ssthresh将会在每收到一个数据包后从初始值开始按照2倍数据段长度的增量持续增长,直到其达到小于所有可用通告窗口内存的某个值停止再增长,增长到该值的位置时,一整窗的数据连同其开销将会完全占满整个rcvbuf 。
3.一个差异:通告窗口大小与通告窗口上限为什么拥塞窗口的慢启动是直接增加的拥塞窗口的值,通告窗口的慢启动并不直接增加通告窗口而是增加的通告窗口的上限呢?
这是因为通告窗口的实际值并非单单由接收缓存溢出检测这么一个因素控制,这个因素事实上反而不是主导因素,主导因素是应用程序是不是即时腾出了接收缓存 。我们从代码中如何确定通告窗口的逻辑中可以看出:
u32 __tcp_select_window(struct sock *sk){struct inet_connection_sock *icsk = inet_csk(sk);struct tcp_sock *tp = tcp_sk(sk);/* MSS for the peer's data.Previous verions used mss_clamp* here.I don't know if the value based on our guesses* of peer's MSS is better for the performance.It's more correct* but may be worse for the performance because of rcv_mss* fluctuations.--SAW1998/11/1*/int mss = icsk->icsk_ack.rcv_mss;// free_space就是应用程序和TCP层合力确定的通告窗口基准值,它简单来讲就是(rcvbuf - sk_rmem_alloc)中的纯数据部分,缩放比例就是本文开始提到的(n-1)/n 。int free_space = tcp_space(sk);int full_space = min_t(int, tp->window_clamp, tcp_full_space(sk));int window;if (mss > full_space)mss = full_space;// 这里是为了防止接收缓存溢出的最后防线,当free_space小于全部rcvbuf按纯数据比例缩放后的大小的一半时,就要小心了!if (free_space < (full_space >> 1)) {icsk->icsk_ack.quick = 0;if (sk_under_memory_pressure(sk))tp->rcv_ssthresh = min(tp->rcv_ssthresh,4U * tp->advmss);// 我们要多大程度上信任mss,取决于发送端mss的波动情况,如注释中所提到的“It's more correct but may be worse for the performance because of rcv_mss fluctuations.”if (free_space < mss)return 0;}// 这里的核心是,虽然应用程序为TCP接收缓存腾出了free_space这么大小的空间,但是并不能全部通告给发送端,需要一点点通告并增加通告的大小,这就是慢启动了 。// 注意这里,free_space不能超过ssthresh,这便是通告窗口上限慢启动的根本了 。if (free_space > tp->rcv_ssthresh)free_space = tp->rcv_ssthresh;...// 这里的窗口计算详细过程反而不是本文关注的,可以参见其它源码分析的文章和书籍...// 最终free_space要落实到window,为了便于理解核心,可以认为free_space就是window 。return window;}最后,总结一幅图,将上面谈到的所有这些概念与Linux内核协议栈TCP实现关联起来:
推荐阅读
- 运维日志分析工具ELK:Windows与Linux皆可安装
- 关于饮食养生的书 论饮食这本书
- Linux 下让工作效率翻倍的四个实用技巧
- 碧螺春,龙井,关于西湖龙井
- 翡翠|关于翡翠的纹和裂
- 有限状态机 多图详解TCP三次握手和四次挥手
- Linux主流架构运维工作简单剖析
- Linux 软链接的使用和具体演示
- 运维人员常用的 Linux 命令汇总
- linux内核调度算法--快速找到最高优先级进程
