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