微信公众号:LinuxerPub 作者:gfree.wind@gmail.com
上篇介绍了TCP接收窗口的初始化,本篇将分析TCP在传输过程中的动态接收窗口大小,由什么决定。
函数tcp_transmit_skb是用于发送本地TCP报文的接口函数,其中包含下面这样的代码。
1if (unlikely(tcb->tcp_flags & TCPHDR_SYN)) {
2 /* 发送SYN报文,即主动连接。接收窗口rcv_wnd在前一节中已经计算得到,这里限制窗口值不能超过65535大小。 */
3 th->window = htons(min(tp->rcv_wnd, 65535U));
4} else {
5 /* 动态的接收窗口由tcp_select_window决定 */
6 th->window = htons(tcp_select_window(sk));
7}
下面进入函数tcp_select_window。
1static u16 tcp_select_window(struct sock *sk)
2{
3 struct tcp_sock *tp = tcp_sk(sk);
4 /* 得到当前的窗口大小,即有效的窗口大小 */
5 u32 cur_win = tcp_receive_window(tp);
6 /* 得到可以提供的窗口值 */
7 u32 new_win = __tcp_select_window(sk);
8 /* 新窗口值决定不能比当前剩余的窗口小。 */
9 if (new_win < cur_win) {
10 /* Danger Will Robinson!
11 * Don't update rcv_wup/rcv_wnd here or else
12 * we will not be able to advertise a zero
13 * window in time. --DaveM
14 *
15 * Relax Will Robinson.
16 */
17 new_win = ALIGN(cur_win, 1 << tp->rx_opt.rcv_wscale);
18 }
19 /* 更新接收滑动窗口的值 */
20 tp->rcv_wnd = new_win;
21 tp->rcv_wup = tp->rcv_nxt;
22 /* 确保新的窗口值不能超过支持的最大值,如系统设置的值或者超过了window scale所支持的最大值 */
23 if (!tp->rx_opt.rcv_wscale && sysctl_tcp_workaround_signed_windows)
24 new_win = min(new_win, MAX_TCP_WINDOW);
25 else
26 new_win = min(new_win, (65535U << tp->rx_opt.rcv_wscale));
27 /* 进行右移window scale。如果不支持window scale,则rcv_wscale为0,右移0位,值不变。*/
28 new_win >>= tp->rx_opt.rcv_wscale;
29 /* 如果新窗口值等于0,则停止快速路径 */
30 if (new_win == 0)
31 tp->pred_flags = 0;
32 return new_win;
33}
从上面的函数可以看成,TCP的动态接收窗口依赖于__tcp_select_window计算的能够提供的新窗口的大小。发送的TCP报文的window窗口,一般情况下等于rcv_wnd,但有些条件下,是可能小于rcv_wnd的。 先看一下tcp_receive_window函数。
1static inline u32 tcp_receive_window(const struct tcp_sock *tp)
2{
3 /*
4 rcv_wup为滑动窗口的左边界, rcv_wnd为接收窗口大小,rcv_nxt为接下来要接收的序号。所以rcv_wup+rcv_wnd-rcv_nxt就是还剩下的窗口大小。
5 因为对端可能push超过我们接收窗口大小的数据,所以win可能小于0。但对于TCP来说,win没有负值,所以要将其重置为0。
6 */
7 s32 win = tp->rcv_wup + tp->rcv_wnd - tp->rcv_nxt;
8 if (win < 0)
9 win = 0;
10 return (u32) win;
11}
接下来查看函数__tcp_select_window。
1u32 __tcp_select_window(struct sock *sk)
2{
3 struct inet_connection_sock *icsk = inet_csk(sk);
4 struct tcp_sock *tp = tcp_sk(sk);
5 int mss = icsk->icsk_ack.rcv_mss;
6 /* 剩余的缓存空间 */
7 int free_space = tcp_space(sk);
8 /* 全部的协议栈缓存空间,但设置其上限为window_clamp。 */
9 int full_space = min_t(int, tp->window_clamp, tcp_full_space(sk));
10 int window;
11 if (mss > full_space)
12 mss = full_space;
13 /* 空闲空间小于全部空间的一半 */
14 if (free_space < (full_space >> 1)) {
15 icsk->icsk_ack.quick = 0;
16 /* 当tcp处于内存压力紧张的情况下,设置接收上限最大为4个MSS */
17 if (tcp_memory_pressure)
18 tp->rcv_ssthresh = min(tp->rcv_ssthresh,
19 4U * tp->advmss);
20 /* 如果空闲空间小于MSS,则窗口直接为0 */
21 if (free_space < mss)
22 return 0;
23 }
24 /* 如果空闲空间大于接收上限,则强制设置空闲空间为rcv_ssthresh */
25 if (free_space > tp->rcv_ssthresh)
26 free_space = tp->rcv_ssthresh;
27 /* 设置窗口值等于当前的接收窗口 */
28 window = tp->rcv_wnd;
29 if (tp->rx_opt.rcv_wscale) {
30 /* 支持windows scale,窗口值为空余空间。 */
31 window = free_space;
32 /* 将窗口按照wscale对齐 */
33 if (((window >> tp->rx_opt.rcv_wscale) << tp->rx_opt.rcv_wscale) != window)
34 window = (((window >> tp->rx_opt.rcv_wscale) + 1)
35 << tp->rx_opt.rcv_wscale);
36 } else {
37 /*
38 如果窗口可能是多个MSS倍数,则取其为mss整数倍。
39 如果mss恰好为全部空间,并且剩余空间要大于当前窗口加上全部空间的一半,才更新窗口为剩余空间。这样可以避免频繁的更新窗口。 */
40 if (window <= free_space - mss || window > free_space)
41 window = (free_space / mss) * mss;
42 else if (mss == full_space &&
43 free_space > window + (full_space >> 1))
44 window = free_space;
45 }
46 return window;
47}
至此,可以总结得出影响传输过程中TCP接收窗口大小的因素: