前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >TCP接收窗口的实现(二)

TCP接收窗口的实现(二)

作者头像
glinuxer
发布2019-04-10 14:57:54
2K0
发布2019-04-10 14:57:54
举报
文章被收录于专栏:专注网络研发专注网络研发

微信公众号:LinuxerPub 作者:gfree.wind@gmail.com

TCP动态接收窗口

上篇介绍了TCP接收窗口的初始化,本篇将分析TCP在传输过程中的动态接收窗口大小,由什么决定。

函数tcp_transmit_skb是用于发送本地TCP报文的接口函数,其中包含下面这样的代码。

代码语言:javascript
复制
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。

代码语言:javascript
复制
 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函数。

代码语言:javascript
复制
 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。

代码语言:javascript
复制
 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接收窗口大小的因素:

  1. 实际剩余的滑动窗口大小,这个为下限;
  2. 实际剩余的缓存空间;
  3. 可以提供的全部缓存空间;
  4. 接收窗口的上限即rcv_ssthreash
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 LinuxerPub 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • TCP动态接收窗口
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档