内核版本:3.2.12

主要源文件:linux-3.2.12/ net/ ipv4/ tcp_veno.c

主要内容:Veno的原理和实现

Author:zhangskd @ csdn blog

概要

Veno结合了Vegas和Reno,故得此名。

Veno的主要目的在于区分随机丢包和无线丢包。

Vegas能够测量网络瓶颈路由器中属于此连接的数据包个数,Veno正是利用这一变量来区分随机丢包和

拥塞丢包,并采取不同的措施。

Veno也改进了窗口增长函数,当网络瓶颈路由器中属于此连接的数据包个数超过一定值时,放缓窗口增长速度。

原理

我们通过3个问题来了解Veno的原理。

(1)如何区分随机丢包和拥塞丢包?

Vegas能够测量网络瓶颈路由器中属于此连接的数据包的个数diff,并以此来进行主动的拥塞控制,避免数据包的丢失。

Veno并不进行主动的拥塞控制,而是利用diff来区分随机丢包和拥塞丢包。

当检测到丢包时:

(1) diff < 3 ,说明此时路由器缓存的数据包不多,路由器很可能没有拥塞,所以判定此丢包为随机丢包。

由于没有发生拥塞,所以拥塞窗口和阈值的减小幅度不宜太大。

拥塞窗口和阈值设置为:0.8 * snd_cwnd。

(2) diff >= 3,说明此时路由器缓存的数据包较多,路由器很可能发生拥塞,所以判定此丢包为拥塞丢包。

由于发生了拥塞,所以拥塞窗口和阈值的减小幅度需要适当加大。

拥塞窗口和阈值设置为:0.5 * snd_cwnd。

(2)拥塞窗口增长函数的改进

(1) diff < 3,说明此时路由器缓存的数据包不多,采用和Reno相同的窗口增长函数,即每个RTT后cwnd增加一个数据包。

(2) diff >= 3,说明此时路由器缓存的数据包较多,不久之后可能发生拥塞,所以应该放缓窗口增长速度,

这样就可以延迟拥塞事件的到来,使cwnd保持在接近网络容量处较长时间,从而达到减少拥塞丢包

和增加网络带宽利用率的效果。

在此期间,每两个RTT才使cwnd增加一个数据包,拥塞窗口增长速度减半。

(3)如何测量网络瓶颈路由器中属于此连接的数据包个数diff?

diiff的测量方法和Vegas中的相同,这里再简单描述下(具体可见前面blog):

期望吞吐量:Expected = cwnd / baseRTT

实际吞吐量:Actual = cwnd / minRTT

吞吐量差值:Diff = Expected - Actual

其中,baseRTT代表当路由器缓存中没有数据包时的RTT值。minRTT为上个RTT的测量值,由于此时路由器中已有数据包,

需要排队,故minRTT > baseRTT。

网络瓶颈路由器中缓存此连接的数据包个数:

diff = Diff * baseRTT = (Expected - Actual) * baseRTT

参数和变量

#define V_PARAM_SHIFT 1
static const int beta = 3 << V_PARAM_SHIFT;

beta是一个阈值,表示网络瓶颈路由器中缓存的属于此连接的数据包的拥塞临界值。

/* Veno variables */

struct veno {
/* 表示是否使用Veno,只有在Open态才能使用。*/
u8 doing_veno_now; /* 在Vegas中是表示上个RTT内得到的RTT样本的个数,但是在Veno中却没用到,
* 形同虚设。
*/
u16 cntrtt; /* 在Vegas中表示上个RTT内得到的RTT样本中的最小值,用于过滤delayed ACK,
* 每个RTT才重置一次。而Veno却每个ACK重置一次,所以代表即时rtt。
*/
u32 minrtt; /* 连接的最小RTT,代表propagation delay。只有在连接刚建立或者idle后才重置,
* 在其它情况下实时更新。
*/
u32 basertt; /* decide whether to increase cwnd,在拥塞避免阶段使用,当diff > 3时,在0和1转换,
* 使cwnd两个RTT才加一。
*/
u32 inc; /* calculate the diff rate,计算网络瓶颈路由器中缓存的属于此连接的数据包的个数,
* 是衡量网络是否处于拥塞状态的标准,从而判断丢包是随机丢包还是无线丢包,
* 判断拥塞避免阶段是否应该放缓拥塞窗口增长速度。
*/
u32 diff;
}

函数

/* There are several situations when we must "re-start" Veno:
* (1) when a connection is establish
* (2) after an RTO
* (3) after fast recovery
* (4) when we send a packet and there is no outstanding unacknowledged
* data (restarting an idle connection)
*/
static inline void veno_enable (struct sock *sk)
{
struct veno *veno = inet_csk_ca(sk); /* turn on Veno */
veno->doing_veno_now = 1;
veno->minrtt = 0x7fffffff;
} static inline void veno_disable (struct sock *sk)
{
struct veno *veno = inet_csk_ca(sk); /* turn off Veno */
veno->doing_veno_now = 0;
} static void tcp_veno_init (struct sock *sk)
{
struct veno *veno = inet_csk_ca(sk); veno->basertt = 0x7fffffff;
veno->inc = 1;
veno_enable(sk);
} static void tcp_veno_state(struct sock *sk, u8 ca state)
{
if (ca_state == TCP_CA_Open)
veno_enable(sk);
else
veno_disable(sk);
} static void tcp_veno_cwnd_event (struct sock *sk, enum tcp_ca_event event)
{
if (event == CA_EVENT_CWND_RESTART || event == CA_EVENT_TX_START)
tcp_veno_init(sk);
}

我们来看一下以上各个状态的转换:

(1)当连接刚建立时,触发CA_EVENT_TX_START事件,调用

tcp_veno_cwnd_event(sk, CA_EVENT_TX_START),其中又调用

tcp_veno_init(sk),做了以下事情:

basertt = 0x7fffffff; /* 重置basertt */

minrtt = 0x7fffffff; /* 重置minrtt */

inc = 1;

doing_veno_now = 1;

(2)从其它拥塞状态进入Open时,调用tcp_veno_state(sk, TCP_CA_Open),

又调用veno_enable(sk),做了以下事情:

doing_veno_now = 1; /* 表示使用veno,在非Open态置为0*/

minrtt = 0x7fffffff; /* 重置minrtt */

(3)从Open态进入其它拥塞状态时,调用tcp_veno_state(sk, ca_state),

ca_state = TCP_CA_Recovery | TCP_CA_Loss,调用veno_disable(sk),

doing_veno_now = 0;

表示在Recovery或Loss等其它非Open状态不能使用Veno。

(4)闲置的连接重新开始传输数据,restarting an idle connection。

触发了CA_EVENT_CWND_RESTART事件,调用

tcp_veno_cwnd_event(sk, CA_EVENT_CWND_RESTART),其中又调用

tcp_veno_init(sk),做的事情和(1)连接刚建立一样。

tcp_veno_pkts_acked()在每收到ACK时调用,用于更新basertt和minrtt。

/* Do rtt sampling needed for Veno. */
static void tcp_veno_pkts_acked (struct sock *sk, u32 cnt, s32 rtt_us)
{
struct veno *veno = inet_csk_ca(sk);
u32 vrtt; if (rtt_us < 0)
return; /* Never allow zero rtt or baseRTT */
vrtt = rtt_us + 1; /* Filter to find propagation delay : */
if (vrtt < veno->basertt)
veno->basertt = vrtt; /* 和vegas的不一样,minrtt并不是上个RTT内的最小采样值,而是即时的rtt,
* 因为每处理一个ACK后,minrtt都置为0x7fffffff。
*/
veno->minrtt = min(veno->minrtt, vrtt); /* 这个cntrtt形同虚设,因为它根本没清零重置过。*/
veno->cntrtt ++;
}

tcp_veno_cong_avoid()为核心函数,主要用于计算diff、根据窗口增长函数调整cwnd。

static void tcp_veno_cong_avoid (struct sock *sk, u32 ack, u32 in_flight)
{
struct tcp_sock *tp = tcp_sk(sk);
struct veno *veno = inet_csk_ca(sk); /* 只允许在Open态使用veno,计算diff和调整cwnd。*/
if (! veno->doing_veno_now) {
tcp_reno_cong_avoid(sk, ack, in_flight);
return;
} /* limited by applications */
if (! tcp_is_cwnd_limited(sk, in_flight))
return; /* 既然cntrtt没清零过,这个判断式毫无意义。*/
if (veno->cntrtt <= 2) {
tcp_reno_cong_avoid(sk, ack, in_flight); } else {
u64 target_cwnd;
u32 rtt; rtt = veno->minrtt; target_cwnd = tp->snd_cwnd * veno->basertt;
target_cwnd <<= V_PARAM_SHIFT;
do_div(target_cwnd, rtt); /* 跟Vegas中diff的计算一样,只是这里乘2 */
veno->diff = (tp->snd_cwnd << V_PARAM_SHIFT) - target_cwnd; /* 处于慢启动阶段*/
if (tp->snd_cwnd <= tp->snd_ssthresh) {
tcp_slow_start(tp); } else { /* 处于拥塞避免阶段 */
/* In the non-congestive state, increase cwnd every rtt。判断网络不处于拥塞,
* 则cwnd的增长和Reno一样。
*/
if (veno->diff < beta) {
tcp_cong_avoid_ai(tp, tp->snd_cwnd); } else {
/* In the congestive state, increasse cwnd every other rtt.
* 判断网络拥塞,则每两个RTT才使cwnd增加一个。
*/
if (tp->snd_cwnd_cnt >= tp->snd_cwnd) {
if (veno->inc && tp->snd_cwnd < tp->snd_cwnd_clamp) {
tp->snd_cwnd++;
veno->inc = 0;
} else
veno->inc = 1;
tp->snd_cwnd_cnt = 0;
} else
tp->snd_cwnd_cnt++;
}
}
if (tp->snd_cwnd < 2)
tp->snd_cwnd = 2;
else if (tp->snd_cwnd > tp->snd_cwnd_clamp)
tp->snd_cwnd = tp->snd_cwnd_clamp;
} /* 重置minrtt,所以minrtt是每个ACK的RTT测量值*/
veno->minrtt = 0x7fffffff;
}

丢包后重新设置慢启动阈值,这就是区分随机丢包和拥塞丢包的函数。

static u32 tcp_veno_ssthresh (struct sock *sk)
{
const struct tcp_sock *tp = tcp_sk(sk);
struct veno *veno = inet_csk_ca(sk); /* in non-congestive state, cut cwnd by 1/5,如果判断为随机丢包,那么慢启动
* 阈值的减小幅度为0.2。
*/
if (veno->diff < beta)
return max(tp->snd_cwnd * 4 / 5, 2U);
else
/* in congestive state, cut cwnd by 1/2,如果判断为拥塞丢包,那么慢启动阈值
* 的减小幅度为0.5。
*/
return max(tp->snd_cwnd >> 1U, 2U);
}

笔者评价

在Vegas中,diff是每个RTT后才计算一次的,因为其中的minrtt的取值是上个RTT内的最小采样值。

这样做得好处是:由于minrtt是多个样本中的最小值,所以可以避免一些异常的RTT样本,比如

delayed ACK。minrtt能代表一段时间内的网络瓶颈路由器的情况,计算得到的diff会更准确一些。

在Veno中,diff是每收到一个ACK计算一次的,因为其中的minrtt的取值是本次ACK的RTT测量值。

这样一来,diff的值可能会有偏差。

这两种方法的区别在于对diff准确性和检测及时性的权衡。

Vegas注重于diff的准确性,所以用一个RTT的时间来测量。

Veno注重于检测的及时性,对每个ACK都做diff计算,为了更及时的检测到网络拥塞。

因为以上原因,Veno可能会出现误判,比如一个ACK的RTT值偏大,却不是由于路由器拥塞,

但是计算得到的diff可能会判定路由器拥塞,这样一些随机丢包可能会被判定成拥塞丢包。

veno在随机丢包率较高的无线网络中有较好的表现。

veno能够区分随机丢包和拥塞丢包,因此避免了随机丢包时不必要的降低吞吐量。

veno改进了窗口增长函数,试图使cwnd停留在接近网络容量处较长时间,延迟丢包事件的到来,

这样不仅能减少丢包,还能提高吞吐量。但实际上这也会导致veno的带宽竞争力变弱。

veno的不足:它对拥塞丢包的判断有着较高的准确率,但是对随机丢包的判断准确率不高,经常

把随机丢包判成拥塞丢包。所以它对随机丢包判断的准确率还有待提高。

此外,beta设为3纯粹是一个经验值,很多场合并不适用。

TCP拥塞控制算法内核实现剖析(十)的更多相关文章

  1. TCP拥塞控制算法 优缺点 适用环境 性能分析

    [摘要]对多种TCP拥塞控制算法进行简要说明,指出它们的优缺点.以及它们的适用环境. [关键字]TCP拥塞控制算法 优点    缺点   适用环境公平性 公平性 公平性是在发生拥塞时各源端(或同一源端 ...

  2. 让人非常easy误解的TCP拥塞控制算法

    正文 非常多人会觉得一个好的TCP拥塞控制算法会让连接加速,这样的观点是错误的.恰恰相反,全部的拥塞控制算法都是为了TCP能够在贪婪的时候悬崖勒马,大多数时候.拥塞控制是减少了数据发送的速度. 我在本 ...

  3. 浅谈TCP拥塞控制算法

    TCP通过维护一个拥塞窗口来进行拥塞控制,拥塞控制的原则是,只要网络中没有出现拥塞,拥塞窗口的值就可以再增大一些,以便把更多的数据包发送出去,但只要网络出现拥塞,拥塞窗口的值就应该减小一些,以减少注入 ...

  4. TCP拥塞控制算法纵横谈-Illinois和YeAH

    周五晚上.终于下了雨.所以也终于能够乱七八糟多写点松散的东西了... 方法论问题. 这个题目太大以至于内容和题目的关联看起来有失偏颇.只是也无所谓,既然被人以为"没有方法论"而歧视 ...

  5. TCP拥塞控制算法

    转自浅谈TCP拥塞控制算法 本篇文章介绍了几种经典的TCP拥塞控制算法,包括算法原理及各自适用场景. 回顾上篇文章:浅谈 redis 延迟 前言 TCP 通过维护一个拥塞窗口来进行拥塞控制,拥塞控制的 ...

  6. 【转载】TCP拥塞控制算法 优缺点 适用环境 性能分析

    [摘要]对多种TCP拥塞控制算法进行简要说明,指出它们的优缺点.以及它们的适用环境. [关键字]TCP拥塞控制算法 优点    缺点   适用环境公平性 公平性 公平性是在发生拥塞时各源端(或同一源端 ...

  7. 网络拥塞控制(三) TCP拥塞控制算法

    为了防止网络的拥塞现象,TCP提出了一系列的拥塞控制机制.最初由V. Jacobson在1988年的论文中提出的TCP的拥塞控制由“慢启动(Slow start)”和“拥塞避免(Congestion  ...

  8. Linux TCP拥塞控制算法原理解析

    这里只是简单梳理TCP各版本的控制原理,对于基本的变量定义,可以参考以下链接: TCP基本拥塞控制http://blog.csdn.net/sicofield/article/details/9708 ...

  9. TCP拥塞控制算法之NewReno和SACK

    TCP拥塞控制算法之NewReno和SACK 2018年05月23日 19:10:03 吃吃爱学习 阅读数:1446    版权声明:程序媛吃吃的博客 https://blog.csdn.net/m0 ...

随机推荐

  1. JSPatch技术文档

    一.背景需求介绍 为什么我们需要一个热修复(hot-fix)技术? 工作中容易犯错.bug难以避免. 开发和测试人力有限. 苹果Appstore审核周期太长,一旦出现严重bug难以快速上线新版本. 作 ...

  2. A.归并排序

    归并排序 (求逆序数) 归并排序:递归+合并+排序 时间复杂度:O(n logn)    空间复杂度:O(n) 用途:1.排序  2.求逆序对数 Description In this problem ...

  3. thinkphp中Conf的配置

    -----www ----------admin -------------Conf ----------admin.php ----------Home -------------Conf ---- ...

  4. JQuery 实现返回顶部效果

    首先要里了解一下几个知识 $(window).scrollTop() ---滚动条距顶部距离 fadeIn() 方法使用淡入效果来显示被选元素,假如该元素是隐藏的. fadeOut() 方法使用淡出效 ...

  5. 蝕刻技術(Etching Technology)

    1. 前言 蚀刻是将材料使用化学反应或物理撞击作用而移除的技术. 蚀刻技术可以分为『湿蚀刻』(wet etching)及『干蚀刻』(dry etching)两类.在湿蚀刻中是使用化学溶液,经由化学反应 ...

  6. haproxy 访问www.zjdev.com 自动跳转到appserver_8001 对应的nginx

    # # acl zjdev_7_req hdr_beg(host) -i www.zjdev.com # use_backend appserver_8001 if zjdev_7_req

  7. kinect for windows - DepthBasics-D2D详解之一

    Depth在kinect中经常被翻译为深度图,指的是图像到摄像头的距离,这些距离数据能让机器知道物理距离有多远.kinect通过两个红外摄像头来实现这个功能的.在这个例子里,就实现了深度图的提取和现实 ...

  8. cocos2dx进阶学习之瓦片地图编辑器

    之前学习了瓦片地图类,现在我们来学习下瓦片地图制作工具 这个是开源的工具,可以从网上下载,下面我们演示下怎么做地图 步骤1 将需要用到的图片放到一个目录下,比如我机器上就是d:\tiled,这些图片是 ...

  9. 基于visual Studio2013解决C语言竞赛题之0408素数

      题目 解决代码及点评 判断一个数是不是素数的方法,一般是看n是不是能被n以内的某个整数(1除外)整除 为了提高效率,这个整数范围一般缩小到n的平方根 如果在这个范围内的整数都不能整除,那么 ...

  10. 复习知识点:GCD多线程

    GCD的基础 #pragma mark - 使用GCD 创建一个 串行 队列 // 第一种:系统提供的创建串行队列的方法 // 在真正的开发中如果需要创建串行队列,比较习惯用这种 // dispatc ...