TCP-Westwood拥塞算法

TCP-Westwood在TCP-Reno的基础上增强了窗口控制和退避处理,例如,TCPW发送端监控ACK报文的接收速率,进而估算当前连接可达到的数据发送速率(可用带宽)。当发送端检测到丢包时(超时或者3个重复ACK),发送端根据估算的发送速率设置拥塞窗口大小(cwnd)和慢启动阈值(ssthresh)。不同于TCP-Reno的窗口减半处理,TCP-Westwood避免太过保守的减低窗口操作,TCP-Westwood称此为:Faster-Recovery。与TCP-Reno相比,TCP-Westwood更适合于无线链路类的TCP连接。

在TCP-Westwood定义的Faster-Recovery阶段,当接收到ACK报文时,意味着对端接收了相应的一定数量的数据报文,将此报文数量除以所经过的时间,即可得到当前带宽的一个评估值。当接收到重复ACK报文(DUPACKs)时,同样意味着对端接收到一个乱序的数据报文,也可用于带宽评估。

尽管如此,发送端并不能确定哪一个数据报文触发了重复ACK报文,进而不能确定其对应的数据报文长度,这时使用一个平均的报文长度值,当之后接收到正常ACK报文之后,进行数量修正。

需要注意的是,在拥塞发生时,连接路径中的瓶颈点应是发生了饱和,其报文缓存已经填满,在此拥塞之前,发送端使用的带宽小于或者等于瓶颈处的可用带宽,所以,TCP-Westwood在调整窗口的时候,重新调整到可用带宽处,而不是简单的将窗口减半。

ACK接收速率的过滤

如果发送端在tk时刻接收到ACK报文,意味着对应的dk大小的数据被对端接收,使用以下公式计算带宽的一个采样值:

b k = d k t k − t k − 1 b_{k} = \frac{d_{k}}{t_{k}-t_{k-1}} bk=tktk1dk

其中tk−1代表前一个ACK报文的接收时间。既然拥塞发生在低频的速率(高频的速率意味着无拥塞)超过链路容量时,我们对采样的速率应用一个低通滤波器来获得可用带宽的低频部分。这样做的也可去除延时ACK带来的速率采样噪声。

低通滤波器的选择十分重要,根据经验,使用Tustin approximation方法将一个连续低通滤波器离散化,得到如下的一个离散时间滤波器:

b ^ k = 2 τ t k − t k − 1 − 1 2 τ t k − t k − 1 + 1 b ^ k − 1 + b k + b k − 1 2 τ t k − t k − 1 + 1 (1) \tag{1}\hat{b}_{k} = \frac{\frac{2\tau }{t_{k}-t_{k-1}}-1}{\frac{2\tau }{t_{k}-t_{k-1}}+1}\hat{b}_{k-1} + \frac{b_{k}+b_{k-1}}{\frac{2\tau }{t_{k}-t_{k-1}}+1} b^k=tktk12τ+1tktk12τ1b^k1+tktk12τ+1bk+bk1(1)

其中bk ˆ为tk时刻的可用带宽的过滤之后的测量值,1/τ为滤波器的截止频率。为理解其工作,假设ACK报文保持恒定的到达间隔,即tk − tk−1 = Δk = τ /10,之后,滤波器(1)变为一个具有恒定系数的滤波器,如下所示:

b ^ k = a b ^ k − 1 + ( 1 − a ) 2 [ b k + b k − 1 ] (2) \tag{2}\hat{b}_{k} = a\hat{b}_{k-1}+\frac{\left ( 1-a \right )}{2}\left [ b_{k}+b_{k-1} \right ] b^k=ab^k1+2(1a)[bk+bk1](2)

其中系数a = 0.9 ,此公式(2)表示一个新的bk ˆ值由90%的前一个值bk−1,和最近两个采样值之和的10%组成。但是,由于ACK的到达时间并不确定,其依赖于tk − tk−1的值,这就要求使用随时间可变的系数。当ACK报文间隔增加时,上一次的值bk-1所占比重应减少;反之,其所占比重应增加,这真是公式(1)所实现。

最后,公式(1)的截止频率为1/τ,这意味值所有高于1/τ的频率部分都过滤掉了。根据Nyquist采样原理,为了采样一个带宽为1/τ的信号,采样间隔需要低于或者等于τ/2。但是,ACK报文是不确定的,如当发送端空闲时,完全没有ACK报文到达,采样频率无法得到保证。因此,如果在最后一个ACK报文接收后,超过τ/m (m≥2)长时间,没有接收到新的ACK报文,滤波器将假设接收到一个虚拟的采样bk=0。如下图所示:

bk-1       bk      bk+1                bk+n-1       bk+n
|          |       |          |        |            |
|----------|-------|----------|--------|------------|-------> t
|          |       |                   |            |
tk-1       tk      tk+1                tk+n-1       tk+n

其中tk-1为最后接收到的ACK报文,tk+j表示虚拟的采样到达的时刻,tk+j+1 - tk+j = τ/m,(j ∈ [0, n-1]),对于此范围内的j值,有bk+j=0。

如果由tk时刻起,一直没有ACK报文,滤波器公式(1)将会以指数级速率趋近于零。

带宽测量

如前所述,DUPACKs应当包含在带宽估算中,因为其意味着报文的成功发送。所以,随后正常的ACK报文应当仅算作确认了一个报文,不应包含重复ACK确认的报文。但是,对于延时ACK,情况就有所不同。TCP-Westwood对于带宽测量提出了以下两点要求:

a. 发送端必须跟踪重复ACK的数量,直到接收了正常ACK。
b. 发送端应当可以检测到延迟ACK。

如下所以,在接收到ACK报文之后,首先需要确定其确认的报文数量(cumul_ack),如果其值为零,表明接收到重复ACK,使用变量accounted_for记录重复ACK数量,将cumul_ack设置为1,即重复ACK表示成功发送了一个报文。

如果cumul_ack大于1,表明此ACK为延迟ACK,或者为发生报文重传之后的正常ACK,此情况下,与已经使用的报文数量(accounted_for)进行比较,如果ACK确认的报文数量小于等于已经使用的报文数量,表明在接收到重复ACK时已经使用了这些报文,进行带宽估算,不应再次使用了,更新accounted_for。反之,表明之后部分报文被使用,其与部分可用,清空accounted_for记录。

最后参与带宽计算的报文数量由变量acked表示。

PROCEDURE AckedCount

cumul_ack = current_ack_seqno - last_ack_seqno;
if (cumul_ack = 0)
    accounted_for=accounted_for+1;
    cumul_ack=1;
endif

if (cumul_ack > 1)
    if (accounted_for >= cumul_ack)
        accounted_for=accounted_for-cumul_ack;
        cumul_ack=1;
    else if (accounted_for < cumul_ack)
        cumul_ack= cumul_ack - accounted_for;
        accounted_for=0;
    endif
endif

last_ack_seqno=current_ack_seqno;
acked=cumul_ack;

return(acked);

END PROCEDURE

TCP-Westwood窗口计算

对于由重复ACK引起的快速重传,慢启动阈值ssthresh等于估算的带宽乘以最小RTT时长,除以报文长度。其中,BWE*RTTmin表示的为瓶颈链路的报文缓存为空时的BDP,此设置允许在拥塞发生之后瓶颈链路的缓存清空。

另外,如果处于拥塞避免阶段,将拥塞窗口设置为ssthresh,反之,对于慢启动阶段,不设置窗口,其仍可按照指数级增长。

if (n DUPACKs are received)
    ssthresh = (BWE*RTTmin)/seg_size;
    if (cwin>ssthresh) /* congestion avoid. */
        cwin = ssthresh;
    endif
endif

对于报文超时引发的情况,慢启动阈值的计算与上相同,这里将其最小值设置为2,将拥塞窗口设置为1。

if (coarse timeout expires)
    ssthresh = (BWE*RTTmin)/seg_size;
    if (ssthresh < 2)
        ssthresh = 2;
    endif;
    cwin = 1;
endif

内核TCP-Westwood初始化

TCP-Westwood更新采样速率的周期不低于50ms(TCP_WESTWOOD_RTT_MIN),即最快50ms更新一次速率。初始的RTT值定义为20秒(TCP_WESTWOOD_INIT_RTT),应当是非常保守了,实际值很少有这么大。

/* TCP Westwood functions and constants */
#define TCP_WESTWOOD_RTT_MIN   (HZ/20)  /* 50ms */
#define TCP_WESTWOOD_INIT_RTT  (20*HZ)  /* maybe too conservative?! */

如下TCP-Westwood拥塞处理结构,其慢启动阈值计算函数ssthresh,和拥塞窗口计算函数cong_avoid函数,以及窗口撤销函数undo_cwnd都是使用TCP-Reno的对应函数。而TCP-Westwood实现的拥塞函数指针主要有cwnd_event、in_ack_event和pkts_acked。

static struct tcp_congestion_ops tcp_westwood __read_mostly = {
    .init       = tcp_westwood_init,
    .ssthresh   = tcp_reno_ssthresh,
    .cong_avoid = tcp_reno_cong_avoid,
    .undo_cwnd      = tcp_reno_undo_cwnd,
    .cwnd_event = tcp_westwood_event,
    .in_ack_event   = tcp_westwood_ack,
    .get_info   = tcp_westwood_info,
    .pkts_acked = tcp_westwood_pkts_acked,

带宽估算

如下函数westwood_filter,如果当前带宽估算值为空,使用第一个带宽采样进行更新。否则,应用低通滤波器函数进行更新。即之前的带宽估算值占比为7/8,新的采样值占比为1/8。

static void westwood_filter(struct westwood *w, u32 delta)
{
    /* If the filter is empty fill it with the first sample of bandwidth  */
    if (w->bw_ns_est == 0 && w->bw_est == 0) {
        w->bw_ns_est = w->bk / delta;
        w->bw_est = w->bw_ns_est;
    } else {
        w->bw_ns_est = westwood_do_filter(w->bw_ns_est, w->bk / delta);
        w->bw_est = westwood_do_filter(w->bw_est, w->bw_ns_est);
    }
}
static inline u32 westwood_do_filter(u32 a, u32 b)
{
    return ((7 * a) + b) >> 3;
}

在窗口更新函数westwood_update_window中,更新带宽估算值。对于第一次进入此函数的情况,first_ack为真(初始化为1),需要同步Westwood记录的SND.UNA。之后,如果上一次带宽估算至今的时长超过Westwood记录的RTT与最小RTT(TCP_WESTWOOD_RTT_MIN=50毫秒)之间的最大值时,更新带宽估算值。参见以上函数westwood_filter。

变量bk中保存了ACK已经确认的数据量,在带宽采样完成之后,将其清空。在此之前,bk的值在函数TCP快速路径函数westwood_fast_bw中累加,在慢速路径中,使用函数tcp_westwood_ack进行累加。

static void westwood_update_window(struct sock *sk)
{
    struct westwood *w = inet_csk_ca(sk);
    s32 delta = tcp_jiffies32 - w->rtt_win_sx;
    
    /* Initialize w->snd_una with the first acked sequence number in order
     * to fix mismatch between tp->snd_una and w->snd_una for the first
     * bandwidth sample
     */
    if (w->first_ack) {
        w->snd_una = tcp_sk(sk)->snd_una;
        w->first_ack = 0;
    }

    /*
     * See if a RTT-window has passed.
     * Be careful since if RTT is less than
     * 50ms we don't filter but we continue 'building the sample'.
     * This minimum limit was chosen since an estimation on small
     * time intervals is better to avoid...
     * Obviously on a LAN we reasonably will always have
     * right_bound = left_bound + WESTWOOD_RTT_MIN
     */
    if (w->rtt && delta > max_t(u32, w->rtt, TCP_WESTWOOD_RTT_MIN)) {
        westwood_filter(w, delta);

        w->bk = 0;
        w->rtt_win_sx = tcp_jiffies32;

如下函数westwood_fast_bw,其在TCP接收的快速路径中调用。首先使用westwood_update_window函数更新带宽估算值(时机未到,不会更新)。其次,更新确认报文统计信息(bk),更新最小RTT值(update_rtt_min)。

static inline void westwood_fast_bw(struct sock *sk)
{
    const struct tcp_sock *tp = tcp_sk(sk);
    struct westwood *w = inet_csk_ca(sk);

    westwood_update_window(sk);

    w->bk += tp->snd_una - w->snd_una;
    w->snd_una = tp->snd_una;
    update_rtt_min(w);
}

更新最小RTT值由如下函数update_rtt_min完成,在TCP初始化时,或者拥塞丢包发生时,需要重新设置最小RTT值(reset_rtt_min)。否则,如果当前RTT小于记录的最小RTT值,更新最小RTT值。当前RTT值在函数tcp_westwood_pkts_acked中更新。

static inline void update_rtt_min(struct westwood *w)
{
    if (w->reset_rtt_min) {
        w->rtt_min = w->rtt;
        w->reset_rtt_min = 0;
    } else
        w->rtt_min = min(w->rtt, w->rtt_min);
}

ACK报文

在接收到ACK确认报文之后,函数tcp_westwood_pkts_acked使用最新的采样,更新Westwood记录的RTT时长。

/*
 * @westwood_pkts_acked
 * Called after processing group of packets.
 * but all westwood needs is the last sample of srtt.
 */
static void tcp_westwood_pkts_acked(struct sock *sk, const struct ack_sample *sample)
{   
    struct westwood *w = inet_csk_ca(sk);
    
    if (sample->rtt_us > 0)
        w->rtt = usecs_to_jiffies(sample->rtt_us);

ACK事件

在ACK报文处理函数tcp_ack中调用此函数,标志CA_ACK_SLOWPATH表示当前TCP连接是否处于慢速路径中。在快速路径中(westwood_fast_bw),使用(tp->snd_una - w->snd_una)计算确认的数据量。但是在慢速路径中,由函数westwood_acked_count确定确认数据量。

static void tcp_westwood_ack(struct sock *sk, u32 ack_flags)
{
    if (ack_flags & CA_ACK_SLOWPATH) {
        struct westwood *w = inet_csk_ca(sk);

        westwood_update_window(sk);
        w->bk += westwood_acked_count(sk);

        update_rtt_min(w);
        return;
    }

    westwood_fast_bw(sk);
}

不同于快速路径,函数westwood_acked_count需要考虑延时ACK(delayed-ack)、重复ACK和部分ACK(partial-ack)的情况。对于重复ACK,确认数据量设定为MSS大小。对于部分ACK,确认数据量也是MSS大小,这里由accounted中需要减去之前重复ACK确认的数据量。

对于延迟ACK或者非部分ACK的情况,确认数据量为减去accounted的结果值。

static inline u32 westwood_acked_count(struct sock *sk)
{
    const struct tcp_sock *tp = tcp_sk(sk);
    struct westwood *w = inet_csk_ca(sk);

    w->cumul_ack = tp->snd_una - w->snd_una;

    /* If cumul_ack is 0 this is a dupack since it's not moving tp->snd_una.
     */
    if (!w->cumul_ack) {
        w->accounted += tp->mss_cache;
        w->cumul_ack = tp->mss_cache;
    }

    if (w->cumul_ack > tp->mss_cache) {
        /* Partial or delayed ack */
        if (w->accounted >= w->cumul_ack) {
            w->accounted -= w->cumul_ack;
            w->cumul_ack = tp->mss_cache;
        } else {
            w->cumul_ack -= w->accounted;
            w->accounted = 0;
        }
    }

    w->snd_una = tp->snd_una;

    return w->cumul_ack;

拥塞事件

先来看一下核心函数tcp_westwood_bw_rttmin,窗口值由带宽估算值与最小RTT值的乘积,除以MSS生成,最小不能低于2。

static u32 tcp_westwood_bw_rttmin(const struct sock *sk)
{
    const struct tcp_sock *tp = tcp_sk(sk);
    const struct westwood *w = inet_csk_ca(sk);

    return max_t(u32, (w->bw_est * w->rtt_min) / tp->mss_cache, 2);
}

如下函数tcp_westwood_event,在发送拥塞时,设置慢启动阈值ssthresh和拥塞窗口。

static void tcp_westwood_event(struct sock *sk, enum tcp_ca_event event)
{
    struct tcp_sock *tp = tcp_sk(sk);
    struct westwood *w = inet_csk_ca(sk);

    switch (event) {
    case CA_EVENT_COMPLETE_CWR:
        tp->snd_cwnd = tp->snd_ssthresh = tcp_westwood_bw_rttmin(sk);
        break;
    case CA_EVENT_LOSS:
        tp->snd_ssthresh = tcp_westwood_bw_rttmin(sk);
        /* Update RTT_min when next ack arrives */
        w->reset_rtt_min = 1;
        break;

内核版本 5.0

相关推荐
随着计算机和通信技术的发展,人们对 Internet 的需求已经越来越超乎想象,因此更 多、更合理的控制机制对现有网络的顺畅运作起着非常重要的作用,其中最基本、最关键 的就是拥塞控制,即如何有效防止或消除网络出现的拥塞,使网络基本运行在轻度拥塞的 最佳状态。 网络中的拥塞来源于网络资源和网络流量分布的不均衡性,它不会随着网络处理能力 的提高而消除。到目前为止,拥塞问题始终没有一个完美的解决方案。面对各种复杂的网 络环境,拥塞控制算法不但在设计方面存在一定的困难,在算法的性能评价方面也都缺乏 统一的标准。根据拥塞控制算法的实现位置,主要分为源端算法和链路算法两种:源端算 法在主机和网络边缘设备中执行,作用是根据反馈信息调整发送速率;链路算法在网络设 备(如路由器和交换机)中执行,作用是检测网络拥塞的发生,产生拥塞反馈信息。拥塞 控制算法设计的关键问题是如何生成反馈信息和如何对反馈信息进行响应。 TCP 协议是使用最广泛的源端算法,也是目前在 Internet 中使用最广泛的传输协议。 它包括慢启动、拥塞避免、快速重传和快速恢复四个阶段,其核心的拥塞避免算法采用一 种 AIMD(加性增加乘性减少)的窗口调节机制。TCP 协议从提出到现在虽然经历了几个 版本的不断改进,但在高带宽时延乘积网络不断扩大的今天,它的局限性也愈加明显,尤 其是 TCP 的拥塞控制算法对大的拥塞窗口响应很慢,发生拥塞时又降低窗口过快的问题。 近几年,在 TCP 协议的基础上提出了一些新的改进协议,如:HSTCP、STCP、H-TCP、 Fast-TCP、BIC 和 CUBIC 等,这些协议公布了它们各自的实现机制和算法,并对可扩展 性、带宽利用率、TCP 友好性、稳定性、RTT 公平性等性能进行衡量和评价,使网络的 性能以及解决拥塞问题的灵敏度等方面得到很大程度地改进和提高。虽然这些新的拥塞控 制协议的算法和实现机制各有千秋,但依然还不能说它们中有哪个能很好地解决现在网络 环境中面临的所有问题,真正实现一个简单又鲁棒性更好的拥塞控制协议,因此,端系统 的拥塞控制协议方面的改进依然在不断深入研究和探索的阶段,尤其在协议参数的修改方 面依然是研究的热点,如何在各个性能之间权衡取舍,以使网络能够运行在最佳状态,仍然值得我们去探讨。 本文从 STCP 和 CUBIC 出发,通过大量不同网络环境下的模拟实验,对它们以及 TCP 协议的性能进 行参照对比,得出各协议的拥塞窗口变化、吞吐量、稳定性、TCP 友好性、RTT 公平性等方面的比较 和分析结果,并从中找到契合点,对总体表现更好些的 CUBIC 协议实施改进。在众多实验结果中我 们发现:基于 CUBIC 协议的运行机制,在 TCP 友好性、RTT 公平性方面都明显优于 STCP,在可扩 展性和稳定性方面也表现出很好的性能,但 CUBIC 的拥塞窗口增长过于保守,且波动幅度与 STCP 相比也相对较大,即 CUBIC 在稳定性方面尚有较大的改进空间。STCP 是以稳定性著称的一种机制简 单的拥塞控制协议,基于其在检测到拥塞后的窗口减小机制与 CUBIC 基本一致,我们想到在保留 CUBIC 原有基本机制的情况下,结合 STCP 的窗口增长机制,将 CUBIC 的窗口增量和 STCP 的窗口 增量糅合,并保持 CUBIC 原有的最大、最小增量的限制机制不变,这样就使 CUBIC 窗口增量在原有 的增量限制范围内做合理且适当的提高,试验证明,这个新改进的算法具有比 CUBIC 更好的稳定性, 并在传承了其在可扩展性、TCP 友好性和 RTT 公平性等优点的同时,也能有所提升,这个改进算法就 叫做——SCUBIC。 主要工作有: 1、阅读参考文献,了解拥塞控制基本理论、发展现状,重点对最近提出的基于端算法的新协议进行理 论分析和总结。 2、利用模拟工具 NS-2 重点对 STCP、CUBIC 协议及 TCP 协议进行模拟实验,并结合理论从其可扩展 性、稳定性、TCP 友好性、RTT 公平性方面进行比较分析。 3、针对 STCP 稳定性和可扩展性比 CUBIC 更加优越的特点,提出一种新的改进算法 SCUBIC。通过 实验验证 SCUBIC 增强了拥塞窗口的稳定和带宽使用的平稳度,较大程度地提高了协议的稳定性,同 时在可扩展性、TCP 友好性和 RTT 公平性方面也有所提升。
©️2020 CSDN 皮肤主题: 编程工作室 设计师:CSDN官方博客 返回首页