1、对于TCP协议数据传输优化来说最大的难点就在于物理带宽的估计,网络的复杂性决定了拥塞控制算法作用的有限性和Linux内核支持的拥塞控制算法(reno、newreno、hybla、bic、cubic、westwood、vegas、yeah等)的多样性,只有准确的带宽估计才能够充分的利用带宽,所以当前的主要难点是带宽估计。
2、BIC 主要用来解决高速大延时网络(High-speed networks with large delays)的带宽估计问题。
3、BIC算法其实是看透了带宽探测、估计的过程,所以其使用了二分查找技术来准确、快速的估计带宽。
BIC二分查找逻辑分析:
如上图,横轴是last_max_cwnd,意义是上次丢包时预估的带宽值,这个值将对下一轮的带宽估计起着关键影响,纵坐标是snd_cwnd,这幅图描述的是BIC在拥塞避免阶段的snd_cwnd调节。
带宽逼近过程(snd_cwnd < last_max_cwnd):
1、dist = last_max_cwnd - snd_cwnd:
2、dist < A,snd_cwnd快逼近带宽值预估值了,snd_cwnd的探测速度要放慢,否则可能会出现丢包。
3、A < dist < B,那么此时应该将snd_cwnd探测速度调节到适中值。
4、dist > B,snd_cwnd离last_max_cwnd距离很远,需要加快它的探测速度。
新带宽探测过程(snd_cwnd > last_max_cwnd):
1、dist = last_max_cwnd - snd_cwnd:
2、如果 dist < C,那么需要慢速探测;
3、如果 C < dist < D,那么中速探测;
4、如果 dist > D,那么应该快速探测。
/* BIC TCP Parameters */
struct bictcp {
u32 cnt; /* increase cwnd by 1 after ACKs */
u32 last_max_cwnd; /* last maximum snd_cwnd */
u32 loss_cwnd; /* congestion window at last loss */
u32 last_cwnd; /* the last snd_cwnd */
u32 last_time; /* time when updated last_cwnd */
u32 epoch_start; /* beginning of an epoch */
#define ACK_RATIO_SHIFT 4
u32 delayed_ack; /* estimate the ratio of Packets/ACKs << 4 */
};
u32cnt; /* increase cwnd by 1 after ACKs */
用来记录进入拥塞避免状态后,需要接收到多少个ACK才能出发snd_cwnd + 1。
u32 last_max_cwnd;/* last maximum snd_cwnd */
丢包的时候,BIC需要选取一个snd_cwnd值作为二分查找的上限值,如果当前二分查找的带宽值大于上一次丢包记录的last_max_cwnd那么last_max_cwnd更新为当前的snd_cwnd,否则为0.9 * snd_cwnd,关于这个问题其实很好理解,如果当前探测到的带宽值比设定的二分阈值小,那么说明当前带宽比之前预计的带宽要小,所以下次预测带宽值要缩小,缩小为原来的0.9倍。
u32 loss_cwnd; /* congestion window at last loss */
用来记录丢包时的拥塞窗口值。
u32 last_time;/* time when updated last_cwnd */
用来记录最近一次拥塞避免阶段snd_cwnd调节的timestamp,如果多次进入时间差很小那么只会调节一次,避免频繁的调节。
u32 epoch_start; /* beginning of an epoch */
用来记录计入拥塞避免状态的timestamp,这个变量在BIC拥塞控制算法具体实现中,没有太大意义。
<span style="font-size:14px;"> </span><span style="font-size:18px;">u32 delayed_ack; /* estimate the ratio of Packets/ACKs << 4 */</span>
用来记录延时ACK的个数,BIC考虑的延时ACK对带宽估计的影响。
/*
* Compute congestion window to use.
*/
static inline void bictcp_update(struct bictcp *ca, u32 cwnd)
{
if (ca->last_cwnd == cwnd &&
(s32)(tcp_time_stamp - ca->last_time) <= HZ / 32)
return;
ca->last_cwnd = cwnd;
ca->last_time = tcp_time_stamp;
if (ca->epoch_start == 0) /* record the beginning of an epoch */
ca->epoch_start = tcp_time_stamp;
/* start off normal */
if (cwnd <= low_window) {
ca->cnt = cwnd;
return;
}
/* binary increase */
if (cwnd < ca->last_max_cwnd) {
__u32 dist = (ca->last_max_cwnd - cwnd)
/ BICTCP_B;
if (dist > max_increment)
/* linear increase */
ca->cnt = cwnd / max_increment;
else if (dist <= 1U)
/* binary search increase */
ca->cnt = (cwnd * smooth_part) / BICTCP_B;
else
/* binary search increase */
ca->cnt = cwnd / dist;
} else {
/* slow start AMD linear increase */
if (cwnd < ca->last_max_cwnd + BICTCP_B)
/* slow start */
ca->cnt = (cwnd * smooth_part) / BICTCP_B;
else if (cwnd < ca->last_max_cwnd + max_increment*(BICTCP_B-1))
/* slow start */
ca->cnt = (cwnd * (BICTCP_B-1))
/ (cwnd - ca->last_max_cwnd);
else
/* linear increase */
ca->cnt = cwnd / max_increment;
}
/* if in slow start or link utilization is very low */
if (ca->loss_cwnd == 0) {
if (ca->cnt > 20) /* increase cwnd 5% per RTT */
ca->cnt = 20;
}
ca->cnt = (ca->cnt << ACK_RATIO_SHIFT) / ca->delayed_ack;
if (ca->cnt == 0) /* cannot be zero */
ca->cnt = 1;
}