Chinaunix首页 | 论坛 | 认证专区 | 博客 登录 | 注册
  • 博客访问: 365407
  • 博文数量: 39
  • 博客积分: 125
  • 博客等级: 民兵
  • 技术积分: 1833
  • 用 户 组: 普通用户
  • 注册时间: 2013-01-03 11:29
个人简介

skills:Linux TCP,C; 欢迎邮件和Q群交流.

文章分类

全部博文(39)

文章存档

2014年(9)

2013年(30)

微信关注

IT168企业级官微



微信号:IT168qiye



系统架构师大会



微信号:SACC2013

订阅
热词专题
TCP窗口行为 2013-11-30 21:44:35

分类: LINUX

作者:henrystark henrystark@126.com
Blog: http://henrystark.blog.chinaunix.net/
日期:20131130
本文可以自由拷贝,转载。但转载请保持文档的完整性,注明原作者及原链接。 如有错讹,烦请指出。

============================================================

TCP窗口行为

这一篇blog讲述TCP在整个传输流程中的窗口变化行为。
TCP窗口行为的关键点有:初始窗口和阈值;何时增窗、增多少;何时减窗、减多少。

1.初始窗口

TCP的拥塞窗口,congestion window,简称cwnd,是拥塞控制的核心,决定一个RTT内发送多少数据包。
在旧的内核版本中,cwnd的初始值设置为2*MSS。在较新的内核版本中,根据Google的建议,设置为10*MSS。
至于为什么这么设置,请参见另一篇blog《增大初始窗口的优劣》。
下面这一段源码是3.2.18内核版本net/ipv4/tcp_input.c中的初始化函数。

__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);
} 

TCPINITCWND在tcp.h中定义。

/* TCP initial congestion window as per draft-hkchu-tcpm-initcwnd-01 */
#define TCP_INIT_CWND       10 

2.6.32.60版本的源码:

/* Numbers are taken from RFC3390.
 *  
 * John Heffner states:
 *
 *  The RFC specifies a window of no more than 4380 bytes
 *  unless 2*MSS > 4380.  Reading the pseudocode in the RFC
 *  is a bit misleading because they use a clamp at 4380 bytes
 *  rather than use a multiplier in the relevant range.
 */
__u32 tcp_init_cwnd(struct tcp_sock *tp, struct dst_entry *dst)
{
    __u32 cwnd = (dst ? dst_metric(dst, RTAX_INITCWND) : 0);

    if (!cwnd) {
        if (tp->mss_cache > 1460)
            cwnd = 2;
        else
            cwnd = (tp->mss_cache > 1095) ? 3 : 4;
    }
    return min_t(__u32, cwnd, tp->snd_cwnd_clamp);
} 

初始阈值ssthresh被设置为无穷大。

#define TCP_INFINITE_SSTHRESH   0x7fffffff 

这里要着重解释一下ssthresh的意义,它的全称是slow start threshold,字面意思就是慢启动阈值。
但这个阈值在拥塞避免阶段也起到关键作用。

2.何时增窗

2.1 慢启动阶段

这个阶段中,cwnd呈指数增长,每收到一个正常ACK,cwnd增加1,经过一个RTT,cwnd增加一倍。
源码位于tcp_cong.c中:

 /*
 298 * Slow start is used when congestion window is less than slow start
 299 * threshold. This version implements the basic RFC2581 version
 300 * and optionally supports:
 301 *      RFC3742 Limited Slow Start        - growth limited to max_ssthresh
 302 *      RFC3465 Appropriate Byte Counting - growth limited by bytes acknowledged
 303 */
 304void tcp_slow_start(struct tcp_sock *tp)
 305{
 306        int cnt; /* increase in packets */
 307
 308        /* RFC3465: ABC Slow start
 309         * Increase only after a full MSS of bytes is acked
 310         *
 311         * TCP sender SHOULD increase cwnd by the number of
 312         * previously unacknowledged bytes ACKed by each incoming
 313         * acknowledgment, provided the increase is not more than L
 314         */
 315        if (sysctl_tcp_abc && tp->bytes_acked < tp->mss_cache)
 316                return;
 317
 318        if (sysctl_tcp_max_ssthresh > 0 && tp->snd_cwnd > sysctl_tcp_max_ssthresh)
 319                cnt = sysctl_tcp_max_ssthresh >> 1;     /* limited slow start */
 320        else
 321                cnt = tp->snd_cwnd;                     /* exponential increase */
 322
 323        /* RFC3465: ABC
 324         * We MAY increase by 2 if discovered delayed ack
 325         */
 326        if (sysctl_tcp_abc > 1 && tp->bytes_acked >= 2*tp->mss_cache)
 327                cnt <<= 1;
 328        tp->bytes_acked = 0;
 329
 330        tp->snd_cwnd_cnt += cnt;
 331        while (tp->snd_cwnd_cnt >= tp->snd_cwnd) {
 332                tp->snd_cwnd_cnt -= tp->snd_cwnd;
 333                if (tp->snd_cwnd < tp->snd_cwnd_clamp)
 334                        tp->snd_cwnd++;
 335        }
 336}
 337EXPORT_SYMBOL_GPL(tcp_slow_start); 

这段代码的重点在while循环,前面的逻辑都是对增窗数目做处理。正常情况下,每收到一个ACK,就调用一次函数,在阈值之下,cwnd指数增长。

2.2 拥塞避免阶段

线性增长,每收到一个ACK,cwnd增加1/cwnd,经过一个RTT,cwnd增加1。因为一个RTT对应一个整窗数据和ACK。

/* In theory this is tp->snd_cwnd += 1 / tp->snd_cwnd (or alternative w) */
 340void tcp_cong_avoid_ai(struct tcp_sock *tp, u32 w)
 341{
 342        if (tp->snd_cwnd_cnt >= w) {
 343                if (tp->snd_cwnd < tp->snd_cwnd_clamp)
 344                        tp->snd_cwnd++;
 345                tp->snd_cwnd_cnt = 0;
 346        } else {
 347                tp->snd_cwnd_cnt++;
 348        }
 349}
 350EXPORT_SYMBOL_GPL(tcp_cong_avoid_ai); 

可以看到,当窗口增长因子cnt大于w,即cwnd时,窗口才增长。
以上这些增长都要判断cwnd是否超过阈值。

3.何时减窗

降窗发生的条件是丢包或者RTT增大。传统TCP在拥塞判断上分为两大阵营:基于丢包,基于RTT变化。
丢包通过重复ACK来反映。而RTT变化通过采样获得。
为了论述简单,这里只讲述基于丢包的窗口降低。至于诸多TCP变种协议如何降窗,在后续blog中论述。
当收到三次重复ACK或者SACK时,sender认为数据包已经丢失,此时开始降窗。cwnd降低为当前数值的一般,ssthresh设定为降低后的cwnd+3。
假设初始阈值为无限大,在cwnd=1000时发生了丢包,则cwnd变为500,ssthresh变为503。
这里需要强调,cwnd并非立即降低,而是逐渐降低,每收到两个ACK,cwnd减1。详情参见另一篇blog《TCP降窗过程》。这里不再列举函数。
基于RTT的协议在RTT有较大变动时,就开始降低,至于如何降低,那是另一个问题了。
此外,可能会有同学疑问,不同版本的TCP协议降窗算法不同,如何实现?
这里简介一下,TCP提供了可以重写的函数,其中包括阈值调节函数,cwnd降低过程由TCP主流程维护,但是诸多版本的拥塞控制算法可以设定降窗下限幅度。
这样就可以控制降窗范围了。
超时的情况另当别论,不属于拥塞控制算法的范围了。

4.概述

从正常TCP流程论述一遍:
TCP初始窗口为2,每收到一个ACK,cwnd+1,以RTT来看,呈现指数增长。此时的阈值为无限大。
遇到拥塞,cwnd降低,阈值重新调节。开始拥塞避免。
发生超时,cwnd重置为1,开始新一轮慢启动。


图片来自http://www.cisco.com/web/CN/products/products_netsol/datacenter/products/wafs/wafs_data_7.html

阅读(3879) | 评论(0) | 转发(3) |
给主人留下些什么吧!~~
评论热议
请登录后评论。

登录 注册