TCP初始拥塞窗口与初始接收窗口

根据RFC3390(2002年)中的表述,TCP初始窗口的上限定义为: min (4 * MSS, max (2*MSS, 4380 bytes)),旨在限制初始窗口在4K范围内,但是当MSS非常大时,会超过4K限制。

       If (MSS <= 1095 bytes)
           then win <= 4 * MSS;
       If (1095 bytes < MSS < 2190 bytes)
           then win <= 4380;
       If (2190 bytes <= MSS)
           then win <= 2 * MSS;

内核函数根据以上的定义选择初始窗口值,MSS值由小到大,窗口值由4降低到2。值4380根据MSS值为1460时,取3倍MSS而来,如果MTU为1500,并且IP和TCP头部都没有选项数据时,MSS值为1460。

  • 首先如果4*MSS小于等于4380字节,即MSS小于4380/4=1095时,窗口值为4;
  • 其次,如果2*MSS小于等于4380字节,即MSS小于等于4380/2=2190时,窗口值为3;这里,如果MSS值大于1460时,按字节计算的窗口值将大于4380,与RFC3390中的定义不符。
  • 最后,如果2*MSS大于4380字节,即MSS大于2190时,窗口值为2。
static inline u32 rfc3390_bytes_to_packets(const u32 smss)
{
	return smss <= 1095 ? 4 : (smss > 2190 ? 2 : 3);
}

在新的RFC6928(2013年)中,初始窗口的上限修改成了如下定义:

min (10*MSS, max (2*MSS, 14600)),

内核函数tcp_init_cwnd用于获取初始窗口,如果路由缓存中记录了初始拥塞窗口值,将优先使用记录值。否则,使用宏TCP_INIT_CWND定义的值10。

__u32 tcp_init_cwnd(const struct tcp_sock *tp, const struct dst_entry *dst)
{
    __u32 cwnd = (dst ? dst_metric(dst, RTAX_INITCWND) : 0);

    if (!cwnd)
        cwnd = TCP_INIT_CWND;
    return min_t(__u32, cwnd, tp->snd_cwnd_clamp);

RFC3390定义的IW值是在2002年,而RFC6928是在2013年,经过十年的时间,无论和连接速率和数据量都有了很大的增长,而且连接的时长变得更短。因此许多的连接在还没有完成慢启动时,已经结束了,并且RFC3390的IW值看起来增大了延时。链接https://www.ietf.org/proceedings/79/slides/tcpm-0对IW10和IW3进行了对比测试。

对于增大的初始窗口值,一般认为TCP的SACK选项功能需要一起使用,以更好的使用。但是在google的测试中,初始窗口IW=10未使用SACK的性能3倍与IW=3并带有SACK选项的情况。

在初始窗口增大到10之后,相应的接收窗口也需要增大到10才能产生预想的结果。默认情况下,窗口值由套接口的接收缓存的大小决定,如下在函数sock_init_data中,套接口的接收缓存大小等于sysctl_rmem_default。

void sock_init_data(struct socket *sock, struct sock *sk)
{
    sk_init_common(sk);

    sk->sk_allocation   =   GFP_KERNEL;
    sk->sk_rcvbuf       =   sysctl_rmem_default;

而sysctl变量sysctl_rmem_default的值为SK_RMEM_MAX,其定义为接收256个大小为256字节的报文所需要的内存空间(其中包括sk_buff结构所需的空间)。

__u32 sysctl_rmem_default __read_mostly = SK_RMEM_MAX;

#define _SK_MEM_PACKETS     256
#define _SK_MEM_OVERHEAD    SKB_TRUESIZE(256)
#define SK_WMEM_MAX     (_SK_MEM_OVERHEAD * _SK_MEM_PACKETS)
#define SK_RMEM_MAX     (_SK_MEM_OVERHEAD * _SK_MEM_PACKETS)

所以PROC文件中rmem_default的大小和sk_buff结构有一定的关系,这导致了不同版本的内核,如果修改了sk_buff,此值将略有不同。如下为内核版本4.10.0中的rmem_default值,大约210k。

$ cat /proc/sys/net/core/rmem_default
212992
$

sk_rcvbuf的大小并不等同于可通告的窗口大小,如下函数tcp_win_from_space,这里有一个比例关系,默认情况下为1:1(sysctl_tcp_adv_win_scale值为1),即sk_rcvbuf的一半可用作通告窗口。按照以上212992计算一半就是106496,大约100k,所以,如果初始拥塞窗口增加到10的话,大约14K的数据,小于100k的接收窗口,是能够处理的。

static inline int tcp_win_from_space(const struct sock *sk, int space)
{
    int tcp_adv_win_scale = sock_net(sk)->ipv4.sysctl_tcp_adv_win_scale;
        
    return tcp_adv_win_scale <= 0 ?
        (space>>(-tcp_adv_win_scale)) : space - (space>>tcp_adv_win_scale);
}     
static inline int tcp_full_space(const struct sock *sk)
{   
    return tcp_win_from_space(sk, sk->sk_rcvbuf);  

以下为内核的窗口初始化函数tcp_select_initial_window,可见,内核将接收窗口设置为MSS值的整数倍,并且在SYN报文中,窗口值不乘以扩展系数,故其值上限为U16_MAX,即一般情况下通过64240的窗口大小。

void tcp_select_initial_window(const struct sock *sk, int __space, __u32 mss,
                   __u32 *rcv_wnd, __u32 *window_clamp, int wscale_ok, __u8 *rcv_wscale, __u32 init_rcv_wnd)
{
    unsigned int space = (__space < 0 ? 0 : __space);
    
	/* Quantize space offering to a multiple of mss if possible. */
    if (space > mss)
        space = rounddown(space, mss);

    if (sock_net(sk)->ipv4.sysctl_tcp_workaround_signed_windows)
        (*rcv_wnd) = min(space, MAX_TCP_WINDOW);
    else
        (*rcv_wnd) = min_t(u32, space, U16_MAX);
         ...

内核版本 5.0

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值