linux內核裏面擁塞控制算法比較多,目前大部分用的cubic算法。
在內核裏面代碼是怎樣實現的呢?
static struct tcp_congestion_ops cubictcp __read_mostly = {
.init = bictcp_init, 初始化
.ssthresh = bictcp_recalc_ssthresh, 更新tcp的ssthresh值
.cong_avoid = bictcp_cong_avoid, 重新計算cwnd值
.set_state = bictcp_state, ca狀態更新
.undo_cwnd = tcp_reno_undo_cwnd, 丟包時候cwnd更新處理
.cwnd_event = bictcp_cwnd_event, 當cwnd事件發生時調用的
.pkts_acked = bictcp_acked, 數據包ack計數
.owner = THIS_MODULE,
.name = "cubic",
};
看下大概回調怎麼實現的
static void bictcp_init(struct sock *sk)
{
struct bictcp *ca = inet_csk_ca(sk);
bictcp_reset(ca);
if (hystart)
bictcp_hystart_reset(sk);
if (!hystart && initial_ssthresh)
tcp_sk(sk)->snd_ssthresh = initial_ssthresh;
}//主要是 ca初始化和ssthresh值初始化
出現擁塞是重新計算ssthresh,就是當前cwnd的一半
static u32 bictcp_recalc_ssthresh(struct sock *sk)
{
const struct tcp_sock *tp = tcp_sk(sk);
struct bictcp *ca = inet_csk_ca(sk);
ca->epoch_start = 0; /* end of epoch */
/* Wmax and fast convergence */
if (tp->snd_cwnd < ca->last_max_cwnd && fast_convergence)
ca->last_max_cwnd = (tp->snd_cwnd * (BICTCP_BETA_SCALE + beta))
/ (2 * BICTCP_BETA_SCALE);
else
ca->last_max_cwnd = tp->snd_cwnd;
return max((tp->snd_cwnd * beta) / BICTCP_BETA_SCALE, 2U);
}
static void bictcp_cong_avoid(struct sock *sk, u32 ack, u32 acked)
{
struct tcp_sock *tp = tcp_sk(sk);
struct bictcp *ca = inet_csk_ca(sk);
if (!tcp_is_cwnd_limited(sk))
return;
if (tcp_in_slow_start(tp)) { //分爲正在慢啓動過程中的計算
if (hystart && after(ack, ca->end_seq))
bictcp_hystart_reset(sk);
acked = tcp_slow_start(tp, acked);
if (!acked)
return;
}
bictcp_update(ca, tp->snd_cwnd, acked);
tcp_cong_avoid_ai(tp, ca->cnt, acked); //正常窗口值 ++
}
u32 tcp_reno_undo_cwnd(struct sock *sk) //丟包時的處理
{
const struct tcp_sock *tp = tcp_sk(sk);
return max(tp->snd_cwnd, tp->prior_cwnd);
}
cubic算法的整體邏輯
TCP的四種擁塞控制算法
1.慢開始 窗口大小達到ssthresh之後慢開始
2.擁塞控制 判斷爲擁塞避免的時候,ssthresh馬上減半
3.快重傳 當出現3個重複的ack的時候,馬上重傳後面一個分組。如下圖
4.快恢復 開始的時候指數級恢復
長肥管道:
有了上面理論基礎之後看下面的問題,當網絡的RTT非常大,已知光速大概30wkm/s,如果是跨國網絡,距離非常遠。RTT很大,這樣一來。來回的ack時間就很長,如果出現丟包馬上就會變成ssthresh減半,慢啓動狀態。窗口恢復非常困難。
tcp 窗口大小就會是這樣的曲線
如何解決這種問題。
調大接收端的tcp_rmem 中間那個缺省值,
echo "4096 52420000 62910000" > /proc/sys/net/ipv4/tcp_rmem
然後再抓包發現初始化的接收窗口確實變大了,用wireshark看起來沒有變大是因爲忽略了後面的WS字段,這個是窗口放大倍數,所以長肥管道爲了保證有足夠的數據在通信信道上跑,就要把接收端的recv buf設置非常大。