关于TCP的接收缓存以及通告窗口,一般而言懂TCP的都能说出个大概,但是涉及到细节的话可能理解就不那么深入了 。
问题:
明明在接收端有8192字节的接收缓存,为什么收了不到8000字节的数据就ZeroWindow了呢?
0.network buffer & Application buffer深入接收缓存管理机制的过程中,你可能会在代码的注释中看到这样的分割,将接收缓存分割成了所谓的network buffer和application buffer,具体参见__tcp_grow_window的注释:
/* 2. Tuning advertised window (window_clamp, rcv_ssthresh)1.通告窗口与接收缓存在TCP的配置中,有一个接收缓存的概念,另外在TCP滑动窗口机制中,还有一个接收窗口的概念,毋庸置疑,接收窗口所使用的内存必须分配自接收缓存,因此二者是包容的关系 。
*
* All tcp_full_space() is split to two parts: "network" buffer, allocated
* forward and advertised in receiver window (tp->rcv_wnd) and
* "application buffer", required to isolate scheduling/application
* latencies from network.
* window_clamp is maximal advertised window. It can be less than
* tcp_full_space(), in this case tcp_full_space() - window_clamp
* is reserved for "application" buffer. The less window_clamp is
* the smoother our behaviour from viewpoint of network, but the lower
* throughput and the higher sensitivity of the connection to losses. 8)
*
* rcv_ssthresh is more strict window_clamp used at "slow start"
* phase to predict further behaviour of this connection.
* It is used for two goals:
* - to enforce header prediction at sender, even when application
* requires some significant "application buffer". It is check #1.
* - to prevent pruning of receive queue because of misprediction
* of receiver window. Check #2.
*
* The scheme does not work when sender sends good segments opening
* window and then starts to feed us spagetti. But it should work
* in common situations. Otherwise, we have to rely on queue collapsing.
*/
然后,几乎所有的分析接收缓存的文章都采用了这种说法,诚然,说法并不重要,关键是要便于人们去理解 。因此我尝试用一种不同的说法去解释它,其实本质上是相同的,只是更加啰嗦一些 。
和我一向的观点一样,本文不会去大段大段分析源码,也就是说不会去做给源码加注释的工作,而是希望能绘制一个关于这个话题的蓝图,就像之前分析OpenVPN以及Netfilter的时候那样 。
但这不是重点,重点是: 接收窗口无法完全占完接收缓存的内存,即接收缓存的内存并不能完全用于接收窗口!Why?
这是因为接收窗口是TCP层的概念,仅仅描述TCP载荷,然而这个载荷之所以可以收到,必须使用一个叫做数据包的载体,在linux中就是skb,另外为了让协议运行,必须为载荷封装TCP头,IP头,以太头...等等 。
我用下图来解释接收缓存以及其和TCP数据包的关系:

文章插图
【注意,当我说“TCP数据包”的时候,我的意思是这是一个带有以太头的完整数据包,当我说“TCP数据段”的时候,我想表达的则是我并不关系IP层及以下的东西 。】
图示的最后,我特意标红了一个“极力要避免”的警示,确实,如果直接把可用的窗口都通告出去了,且发送端并不按照满MSS发送的话,是存在溢出风险的,这要怎么解决呢?
附:如何确定通告窗口可以使用的接收缓存
在代码中,我们注意一个函数tcp_fixup_rcvbuf:
static void tcp_fixup_rcvbuf(struct sock *sk){u32 mss = tcp_sk(sk)->advmss;u32 icwnd = TCP_DEFAULT_INIT_RCVWND;int rcvmem;/* Limit to 10 segments if mss <= 1460,* or 14600/mss segments, with a minimum of two segments.*/if (mss > 1460)icwnd = max_t(u32, (1460 * TCP_DEFAULT_INIT_RCVWND) / mss, 2);rcvmem = SKB_TRUESIZE(mss + MAX_TCP_HEADER);// 将rcvbuf按比例缩放到其(n-1)/n可以完全容纳TCP纯载荷的程度,n由系统参数net.ipv4.tcp_adv_win_scale来确定 。while (tcp_win_from_space(rcvmem) < mss)rcvmem += 128;rcvmem *= icwnd;if (sk->sk_rcvbuf < rcvmem)sk->sk_rcvbuf = min(rcvmem, sysctl_tcp_rmem[2]);}以上函数确定了接收缓存,其中有3个要点:1).初始通告窗口的大小
默认是10个MSS满1460字节的段,这个数值10来自google的测试,与拥塞窗口的初始值一致,然而由于MSS各不同,其会按照1460/mss的比例进行缩放来适配经验值10 。
2).TCP载体开销的最小值128
展开宏SKB_TRUESIZE会发现其最小值就是128,这对通告窗口慢启动过程定义了一个安全下界,载荷小于128字节的TCP数据段将不会增加通告的上限大小 。
推荐阅读
- 运维日志分析工具ELK:Windows与Linux皆可安装
- 关于饮食养生的书 论饮食这本书
- Linux 下让工作效率翻倍的四个实用技巧
- 碧螺春,龙井,关于西湖龙井
- 翡翠|关于翡翠的纹和裂
- 有限状态机 多图详解TCP三次握手和四次挥手
- Linux主流架构运维工作简单剖析
- Linux 软链接的使用和具体演示
- 运维人员常用的 Linux 命令汇总
- linux内核调度算法--快速找到最高优先级进程
