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

TCP接收窗口的实现(一)

作者头像
glinuxer
发布2019-04-10 11:03:29
2.7K0
发布2019-04-10 11:03:29
举报
文章被收录于专栏:专注网络研发专注网络研发

TCP首部中的Window字段,表示当前套接字的接收窗口,即目前可以接收的数据大小,对端不会发送超过接收窗口大小的数据。如果在三次握手时,两端都支持Windows Scale选项,则实际的接收窗口还要乘以Windows Scale的值。

这个主题将分为两部分:本文是第一部分,是TCP的初始接收窗口大小是如何决定的。第二部分,分析TCP的动态接收窗口。

主动连接

TCP主动发起连接,即发送三次握手中的第一个SYN报文。这时,TCP窗口的大小自然取决于本地的参数。函数tcp_connect_init负责初始化TCP连接,包括window的大小。

代码语言:javascript
复制
 1static void tcp_connect_init(struct sock *sk)
 2{
 3        const struct dst_entry *dst = __sk_dst_get(sk);
 4        struct tcp_sock *tp = tcp_sk(sk);
 5        __u8 rcv_wscale;
 6        /* We'll fix this up when we get a response from the other end.
 7         * See tcp_input.c:tcp_rcv_state_process case TCP_SYN_SENT.
 8         */
 9        /* 根据系统配置参数,计算该套接字将要发送的TCP首部大小。 */
10        tp->tcp_header_len = sizeof(struct tcphdr) +
11                (sysctl_tcp_timestamps ? TCPOLEN_TSTAMP_ALIGNED : 0);
12#ifdef CONFIG_TCP_MD5SIG
13        if (tp->af_specific->md5_lookup(sk, sk) != NULL)
14                tp->tcp_header_len += TCPOLEN_MD5SIG_ALIGNED;
15#endif
16        /* 如果用户配置MSS大小,将其保存下来,作为MSS的最大值 */
17        if (tp->rx_opt.user_mss)
18                tp->rx_opt.mss_clamp = tp->rx_opt.user_mss;
19    /* max_window表示对端window的最大值 */
20        tp->max_window = 0;
21    /* 初始化MTU探测 */
22        tcp_mtup_init(sk);
23    /* 利用出口的MTU同步MSS */
24        tcp_sync_mss(sk, dst_mtu(dst));
25    /* 如果没有设置windows的最大值,则尝试使用下一跳的最大窗口值 */
26        if (!tp->window_clamp)
27                tp->window_clamp = dst_metric(dst, RTAX_WINDOW);
28    /* 出口的MSS值即握手时交换的MSS值 */
29        tp->advmss = dst_metric_advmss(dst);
30    /* 
31    如果应用层配置MSS,并且小于交换的MSS值,则将选择用户配置的MSS。
32    对于TCP协议来说,重要的是要保证MSS是有效的。当用户配置的MSS值大于出口的MSS时,为了保证TCP数据报文的正常通信,则选择较小的值。
33    */
34        if (tp->rx_opt.user_mss && tp->rx_opt.user_mss < tp->advmss)
35                tp->advmss = tp->rx_opt.user_mss;
36    /* 初始化接收MSS,其实是对对端MSS的猜测判断 */
37        tcp_initialize_rcv_mss(sk);
38        /* limit the window selection if the user enforce a smaller rx buffer */
39    /* 如果用户设置接收缓存的大小,并且窗口的最大值大于了接收缓存或者没有设置窗口的上限,则设置窗口的最大值为接收缓存的大小。*/
40        if (sk->sk_userlocks & SOCK_RCVBUF_LOCK &&
41            (tp->window_clamp > tcp_full_space(sk) || tp->window_clamp == 0))
42                tp->window_clamp = tcp_full_space(sk);
43    /* 选择初始的接收窗口 */
44        tcp_select_initial_window(tcp_full_space(sk),
45                                  tp->advmss - (tp->rx_opt.ts_recent_stamp ? tp->tcp_header_len - sizeof(struct tcphdr) : 0),
46                                  &tp->rcv_wnd,
47                                  &tp->window_clamp,
48                                  sysctl_tcp_window_scaling,
49                                  &rcv_wscale,
50                                  dst_metric(dst, RTAX_INITRWND));
51        …… ……
52}

接下来进入tcp_select_initial_window函数

代码语言:javascript
复制
 1void tcp_select_initial_window(int __space, __u32 mss,
 2                               __u32 *rcv_wnd, __u32 *window_clamp,
 3                               int wscale_ok, __u8 *rcv_wscale,
 4                               __u32 init_rcv_wnd)
 5{
 6        unsigned int space = (__space < 0 ? 0 : __space);
 7        /* If no clamp set the clamp to the max possible scaled window */
 8    /* 
 9    如果没有设置窗口最大值,则将其设置为协议规定的最大值。Windows Scale的最大限制为14,则TCP协议支持的最大窗口值为(65535 << 14)字节。
10    对于SYN包来说,*windows_clamp等于套接字的缓存大小或者用户配置的窗口上限。
11    */
12        if (*window_clamp == 0)
13                (*window_clamp) = (65535 << 14);
14    /* space最大不能超过窗口上限大小*window_clamp */
15        space = min(*window_clamp, space);
16    /* 保证space是mss的整数倍 */
17        if (space > mss)
18                space = (space / mss) * mss;
19        /* NOTE: offering an initial window larger than 32767
20         * will break some buggy TCP stacks. If the admin tells us
21         * it is likely we could be speaking with such a buggy stack
22         * we will truncate our initial window offering to 32K-1
23         * unless the remote has sent us a window scaling option,
24         * which we interpret as a sign the remote TCP is not
25         * misinterpreting the window field as a signed quantity.
26         */
27    /* 如果需要与将windows大小视为有符号数的对端通信,则接收窗口的大小最大不能超过MAX_TCP_WINDOW(32767)。不然对端可能将其视为负数。 */
28        if (sysctl_tcp_workaround_signed_windows)
29                (*rcv_wnd) = min(space, MAX_TCP_WINDOW);
30        else
31                (*rcv_wnd) = space;
32        (*rcv_wscale) = 0;
33        if (wscale_ok) {
34        /* 系统支持windows scale,下面来确定windows scale可能支持的最大值。先确定可能最大的接收缓存,然后通过移位得到最大支持的scale值。 */
35                space = max_t(u32, space, sysctl_tcp_rmem[2]);
36                space = max_t(u32, space, sysctl_rmem_max);
37                space = min_t(u32, space, *window_clamp);
38        /* 这时得到了缓存的可能的最大值 */
39                while (space > 65535 && (*rcv_wscale) < 14) {
40                        space >>= 1;
41                        (*rcv_wscale)++;
42                }
43        }
44        if (mss > (1 << *rcv_wscale)) {
45        /* 初始化拥塞窗口 */
46                int init_cwnd = TCP_DEFAULT_INIT_RCVWND;
47        /* 当MSS大于1460字节时,要限制拥塞窗口不能太大。最小是2个MSS,最大值按照MSS为1460来运算。 */
48                if (mss > 1460)
49                        init_cwnd =
50                        max_t(u32, (1460 * TCP_DEFAULT_INIT_RCVWND) / mss, 2);
51        /* 
52        计算初始接收窗口。
53        对于SYN包来说,init_rcv_wnd即dst_metric(dst, RTAX_INITRWND)。一般来说,在未配置dst的接收窗口时,取当前*rcv_wnd和拥塞窗口的较小值。
54         */
55                if (init_rcv_wnd)
56                        *rcv_wnd = min(*rcv_wnd, init_rcv_wnd * mss);
57                else
58                        *rcv_wnd = min(*rcv_wnd, init_cwnd * mss);
59        }
60    /* 最大的窗口值不能超过可表示值 */
61        /* Set the clamp no higher than max representable value */
62        (*window_clamp) = min(65535U << (*rcv_wscale), *window_clamp);
63}

至此,主动连接已经完成了初始窗口的选择。

被动连接

下面看看TCP被动连接时,如何选择窗口大小。函数tcp_make_synack是用于生成三次握手中的syn+ack报文,下面请看它的部分代码。

代码语言:javascript
复制
 1        if (req->rcv_wnd == 0) {
 2                __u8 rcv_wscale;
 3        /* 设置窗口的上限 */
 4                req->window_clamp = tp->window_clamp ? : dst_metric(dst, RTAX_WINDOW);
 5                /* 根据接收缓存的大小设置窗口的上限 */
 6                if (sk->sk_userlocks & SOCK_RCVBUF_LOCK &&
 7                    (req->window_clamp > tcp_full_space(sk) || req->window_clamp == 0))
 8                        req->window_clamp = tcp_full_space(sk);
 9                /* 通过tcp_select_initial_window选择初始窗口 */
10                tcp_select_initial_window(tcp_full_space(sk),
11                        mss - (ireq->tstamp_ok ? TCPOLEN_TSTAMP_ALIGNED : 0),
12                        &req->rcv_wnd,
13                        &req->window_clamp,
14                        ireq->wscale_ok,
15                        &rcv_wscale,
16                        dst_metric(dst, RTAX_INITRWND));
17                ireq->rcv_wscale = rcv_wscale;
18        }

函数tcp_select_initial_window在前文中已经分析过了,这里不再重复。

通过上面两种情况,我们已经可以总结出TCP初始窗口值大小取决于哪几个因素:

  1. 套接字接收缓存的大小;
  2. 用户设置的window上限;
  3. 出口设置的window上限;
  4. 拥塞窗口的初始大小。

而一般来说,大家都很少设置window上限,所以TCP初始接收窗口的大小就决定于套接字的缓存和拥塞窗口(这个由系统决定)。


居安思危,手不释卷。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-03,如有侵权请联系 cloudcommunity@tencent.com 删除

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

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

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

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