TCP數據的發送和接收

本篇文章結合實驗對 TCP 數據傳輸中的重傳機制、滑動窗口以及擁塞控制做簡要的分析學習。

重傳

實驗環境

這里使用兩臺騰訊云服務器:vm-1(172.19.0.3)和vm-2(172.19.0.6)。

超時重傳

首先 vm-1 作為服務端啟動 nc,然后開啟抓包,并使用 netstat 查看連接狀態:

$ nc -k -l 172.19.0.3 9527# 新開一個終端開啟抓包
$ sudo tcpdump -s0 -X -nn "tcp port 9527" -w tcp.pcap --print# 新開一個終端查看連接狀態
$ while true; do sudo netstat -anpo | grep 9527 | grep -v LISTEN; sleep 1; done

然后我們在 vm-2 上使用 nc 連接 vm-1,三次握手成功后使用 iptables 攔截所有 vm-1 發來的包。

$ nc 172.19.0.3 9527# 新開一個終端使用 iptables 攔截所有 vm-1 發來的包
$ sudo iptables -A INPUT -p tcp --sport 9527 -j DROP

準備好后我們從 vm-1 輸入 abc 按下回車, vm-2 的 iptables 會將包丟棄,因此會觸發 vm-1 進行重傳,我們來看下 vm-1 的網絡連接狀態以及抓包結果:

  • 網絡連接狀態
tcp        0      0 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            off (0.00/0/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (0.30/1/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (0.08/2/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (0.72/3/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (2.96/4/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (6.35/5/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (12.31/6/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (25.12/7/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (50.24/8/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (101.48/9/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (119.18/10/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (119.30/11/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (119.41/12/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (119.54/13/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (119.66/14/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (119.80/15/0)
...
  • 抓包結果

1. RTO 計算算法

三次握手后第 4 個包發送數據,其 length 為 4,我們輸入了 abc 并按下回車,剛好四個字節,因為客戶端收不到包,因此后續觸發了重傳。

TCP 重傳是基于時間來判斷的,這里有兩個概念:

  • RTO(Retransmission TimeOut):重傳超時時間
  • RTT(Round Trip Time):往返時間

TCP 會根據 RTT 來動態的計算 RTO,如果超時 RTO 會采用指數退避原則進行指數級增長,但最大不超過 120s。我們先來回顧下 RTO 的計算算法:

經典算法

RFC 793 中定義的 RTO 計算算法如下:

  1. 記錄初始的幾次 RTT 值
  2. 計算平滑 RTT 值(SRTT,Smoothed RTT),計算公式為如下:
# alpha 為平滑因子,取值在 0.8 到 0.9 之間,Linux 內核中默認是 0.875
SRTT = ( ALPHA * SRTT ) + ((1-ALPHA) * RTT)

可以看到,如果 alpha 值越大,標識系統越信任之前的計算結果,否則就會更信任新的 RTT 值。

  1. 計算 RTO 值,計算公式為如下:
RTO = min[Ubound,max[Lbound,(BETA*SRTT)]]
  • Ubound 為 RTO 上限,Linux 內核中默認是 120s
  • Lbound 為 RTO 下限,Linux 內核中默認是 200ms
  • Beta 為延遲方差因子,取值在 1.3 到 2.0 之間。
Karn 算法

上述算法的問題在于將所有包的 RTT 一視同仁,是對于重傳的包,如果取第一次發送+ACK 包的 RTT 值,會導致 RTT 明顯偏大;如果取重傳的包,此時如果之前的 ACK 響應回來了,又會導致取值偏小。

為此 1987 年 Phil Karn/Craig Partridge 在論文 Improving Round-Trip Time Estimates in Reliable Transport Protocols 中提出了 Karn 算法,其最大的特點是將重傳的包忽略掉,不用來做 RTT 的計算,同時一旦重傳,RTO 會立即翻倍。

rfc6298 中規定,RTT 的采用必須采用 Karn 算法。

Jacobson/Karels 算法

RFC2988 中改進了重傳算法,并在 rfc6298 中進行了更新,其規定的 RTO 計算算法如下:

對于初始 RTO,當第一個包的 RTT 獲取到后:
SRTT = RTT
RTTVAR = RTT / 2
RTO = SRTT + max(K*RTTVAR, G) where K = 4 and G = 200ms對于后續的 RTO 值計算,獲取到新的 RTT 后:
RTTVAR = (1-Beta)*RTTVAR + Beta*|SRTT - RTT|
SRTT = (1-Alpha)*SRTT + Alpha*RTT最后 RTO 的計算公式為:RTO = SRTT + max(K*RTTVAR, G)

在 Linux 中,Alpha 取值為 0.125,Beta 取值為 0.25,K 取值為 4,G 取值為 200ms,其次還做了一些工程上的優化,這里先不深究,具體源碼參考tcp_rtt_estimator 和 tcp_set_rto。

RTO 與 Delayed ACK

我們可以通過 ss -tip 命令查看某個連接的 rto,可以看到我們的連接初始 RTO 為 200ms,每次超時重傳后都會翻倍,一直增長到 120s 后固定不變。

# 初始 RTO 為 200msESTAB 0      0               172.19.0.3:9527                172.19.0.6:41278 users:(("nc",pid=490833,fd=4))cubic wscale:7,7 rto:200 rtt:0.153/0.076 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:10 segs_in:2 send 4.42Gbps lastsnd:11221 lastrcv:11221 lastack:11221 pacing_rate 8.83Gbps delivered:1 app_limited rcv_space:57076 rcv_ssthresh:57076 minrtt:0.153 snd_wnd:59264ESTAB 0      4               172.19.0.3:9527                172.19.0.6:41278 users:(("nc",pid=490833,fd=4))cubic wscale:7,7 rto:12800 backoff:6 rtt:0.153/0.076 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:1 ssthresh:7 bytes_sent:32 bytes_retrans:28 segs_out:8 segs_in:2 data_segs_out:8 send 442Mbps lastsnd:1115 lastrcv:28668 lastack:28668 pacing_rate 8.83Gbps delivered:1 app_limited busy:14438ms unacked:1 retrans:1/7 lost:1 rcv_space:57076 rcv_ssthresh:57076 minrtt:0.153 snd_wnd:59264ESTAB 0      4               172.19.0.3:9527                172.19.0.6:41278 users:(("nc",pid=490833,fd=4))cubic wscale:7,7 rto:51200 backoff:8 rtt:0.153/0.076 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:1 ssthresh:7 bytes_sent:40 bytes_retrans:36 segs_out:10 segs_in:2 data_segs_out:10 send 442Mbps lastsnd:45728 lastrcv:112705 lastack:112705 pacing_rate 8.83Gbps delivered:1 app_limited busy:98475ms unacked:1 retrans:1/9 lost:1 rcv_space:57076 rcv_ssthresh:57076 minrtt:0.153 snd_wnd:59264ESTAB 0      4               172.19.0.3:9527                172.19.0.6:41278 users:(("nc",pid=490833,fd=4))cubic wscale:7,7 rto:102400 backoff:9 rtt:0.153/0.076 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:1 ssthresh:7 bytes_sent:44 bytes_retrans:40 segs_out:11 segs_in:2 data_segs_out:11 send 442Mbps lastsnd:2475 lastrcv:124748 lastack:124748 pacing_rate 8.83Gbps delivered:1 app_limited busy:110518ms unacked:1 retrans:1/10 lost:1 rcv_space:57076 rcv_ssthresh:57076 minrtt:0.153 snd_wnd:59264$ sudo ss -tip | grep -A 1 9527
ESTAB 0      4               172.19.0.3:9527                172.19.0.6:41278 users:(("nc",pid=490833,fd=4))cubic wscale:7,7 rto:120000 backoff:10 rtt:0.153/0.076 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:1 ssthresh:7 bytes_sent:48 bytes_retrans:44 segs_out:12 segs_in:2 data_segs_out:12 send 442Mbps lastsnd:4544 lastrcv:233313 lastack:233313 pacing_rate 8.83Gbps delivered:1 app_limited busy:219083ms unacked:1 retrans:1/11 lost:1 rcv_space:57076 rcv_ssthresh:57076 minrtt:0.153 snd_wnd:59264$ sudo ss -tip | grep -A 1 9527
ESTAB 0      4               172.19.0.3:9527                172.19.0.6:41278 users:(("nc",pid=490833,fd=4))cubic wscale:7,7 rto:120000 backoff:15 rtt:0.153/0.076 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:1 ssthresh:7 bytes_sent:68 bytes_retrans:64 segs_out:17 segs_in:2 data_segs_out:17 send 442Mbps lastsnd:2520 lastrcv:845689 lastack:845689 pacing_rate 8.83Gbps delivered:1 app_limited busy:831459ms unacked:1 retrans:1/16 lost:1 rcv_space:57076 rcv_ssthresh:57076 minrtt:0.153 snd_wnd:59264

從 ss 的信息中可以看到雖然 RTT 的大小始終是 rtt:0.153/0.076 ,代表 rtt 時間為 0.153ms,平均偏差為 0.076ms,但 RTO 時間最小也是 200ms,后續一直增加到120000 ms,看起來和 RTT 并沒有關系。

這樣是因為 Linux 內核規定了 RTO 的最小值和最大值分別為 200ms 和 120s,具體源碼如下:

// 源碼地址:https://elixir.bootlin.com/linux/v6.0/source/include/net/tcp.h#L141
#define TCP_RTO_MAX     ((unsigned)(120*HZ)) 
#define TCP_RTO_MIN     ((unsigned)(HZ/5))

HZ 表示 CPU 一秒種發出多少次時間中斷–IRQ-0,通常使用 HZ 做時間片的單位,可以理解為 1HZ 就是 1s。

$ cat /boot/config-`uname -r` | grep '^CONFIG_HZ='
CONFIG_HZ=1000# ubuntu @ vm-1 in ~ [15:44:15]
$ cat /proc/interrupts | grep timer && sleep 1 && cat /proc/interrupts | grep timer
LOC:  134957597  148734818   Local timer interrupts
LOC:  134957987  148735153   Local timer interrupts

這樣做主要是為了給 Delayed ACK 留出時間。簡單來說就是讓 TCP 在收到數據包后稍微等一會,看有沒有其他需要發送的數據,如果有就讓 ACK 搭個便車一起發送回去,這樣可以減少網絡上小包的數量,提高網絡傳輸效率。

重傳超時時長

netstat 查看狀態可以看到重傳計時器在不斷變化,從 200ms 開始不斷翻倍,最終在傳完 10 次后固定為 120s,最終顯示已經重傳了 15 次 on (119.80/15/0)。這里主要受 tcp_retries2 參數的控制,默認為 15。注意這里不是精確控制一定會重傳 15 次,而是 tcp_retries2 結合 TCP_RTO_MIN(200ms)計算出一個超時時間來,tcp 連接不斷重傳,最終不能超過這個超時時間。源碼如下:


// 源碼地址:https://elixir.bootlin.com/linux/v6.0/source/net/ipv4/tcp_timer.c#L231
static int tcp_write_timeout(struct sock *sk)
{// ... 代碼省略bool expired = false, do_reset;int retry_until = READ_ONCE(net->ipv4.sysctl_tcp_retries2);if (!expired)expired = retransmits_timed_out(sk, retry_until,icsk->icsk_user_timeout);if (expired) {/* Has it gone just too far? */tcp_write_err(sk);return 1;} 
}
// 源碼地址:https://elixir.bootlin.com/linux/v6.0/source/net/ipv4/tcp_timer.c#L209
static bool retransmits_timed_out(struct sock *sk,unsigned int boundary,unsigned int timeout)
{// ... 代碼省略unsigned int start_ts;unsigned int rto_base = TCP_RTO_MIN;timeout = tcp_model_timeout(sk, boundary, rto_base);return (s32)(tcp_time_stamp(tcp_sk(sk)) - start_ts - timeout) >= 0;
}// 源碼地址:https://elixir.bootlin.com/linux/v6.0/source/net/ipv4/tcp_timer.c#L182
static unsigned int tcp_model_timeout(struct sock *sk,unsigned int boundary,unsigned int rto_base)
{unsigned int linear_backoff_thresh, timeout;linear_backoff_thresh = ilog2(TCP_RTO_MAX / rto_base);if (boundary <= linear_backoff_thresh)timeout = ((2 << boundary) - 1) * rto_base;elsetimeout = ((2 << linear_backoff_thresh) - 1) * rto_base +(boundary - linear_backoff_thresh) * TCP_RTO_MAX;return jiffies_to_msecs(timeout);
}

可以看到內核取 tcp_retries2 參數值作為 boundary,核心計算邏輯位于 tcp_model_timeout 函數中,首先會計算出小于 120s 時的指數退避次數為 9。因此重傳次數在小于等于 9 次時,下一次的重傳時間都是指數增加的,如果超過 9 次比如已經發生了 10 次重傳,那下一次的重傳時間就是 120s 了。從 netstat 的輸出中我們可以驗證這一點:

tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (101.48/9/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:41278        ESTABLISHED 490833/nc            on (119.18/10/0)

總超時的計算邏輯為:

  • tcp_retries2 <= 9 時, timeout = ((2 << boundary) - 1) * rto_base
  • tcp_retries2 > 9 時, timeout = ((2 << linear_backoff_thresh) - 1) * rto_base + (boundary - linear_backoff_thresh) * TCP_RTO_MAX;

基于上述邏輯,在 rto 為 200ms時,我們可以計算出 tcp_retries2 設置和總重傳超時時間的關系:

tcp_retries2重傳超時時間總超時時間
0200ms200ms
1400ms600ms
2800ms1.4s
31.6s3s
43.2s6.2s
56.4s12.6s
612.8s25.4s
725.6s51s
851.2s102.2s
9102.4s204.6s
10120s324.6s
11120s444.6s
12120s564.6s
13120s684.6s
14120s804.6s
15120s924.6s

tcp_retries2 默認是 15,因此默認情況下,TCP 發送數據失敗后大約會在 924.6s,也就是 15 分鐘左右才會放棄連接。如果實際 RTO 很大,也不會真的重傳 15 次導致等待時間過長,而是在超過 924.6s 后放棄連接。下面我們使用 tc qdisc 將 vm-2 的延遲改為 2s 來模擬網絡延遲在來看下重傳的次數:

# ubuntu @ vm-2 in ~ [10:05:28]
$ sudo tc qdisc add dev eth0 root netem delay 2000ms

修改完成后重新建立連接并發送數據,通過 ss、netstat 查看,可以看到初始 RTO 已經成了 6s,抓包顯示實際的重傳次數為 11 次,超時時長為 973.2567 - 45.5127 = 927.744s,大約 15 分鐘多一些,基本符合預期。

# 初始 RTO 為 6s
$ sudo ss -tip | grep -A 1 9527
ESTAB 0      0               172.19.0.3:9527                172.19.0.6:36856 users:(("nc",pid=1880252,fd=4))cubic wscale:7,7 rto:6000 rtt:2000/1000 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:10 segs_in:3 send 338kbps lastsnd:25355 lastrcv:25355 lastack:24330 pacing_rate 676kbps delivered:1 app_limited retrans:0/1 rcv_space:57076 rcv_ssthresh:57076 minrtt:2000 snd_wnd:59264# 超時時間翻倍到 120s 后,RTO 也變為 120000ms
$ sudo ss -tip | grep -A 1 9527
ESTAB 0      4               172.19.0.3:9527                172.19.0.6:39054 users:(("nc",pid=1910324,fd=4))cubic wscale:7,7 rto:120000 backoff:5 rtt:2000/1000 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:1 ssthresh:7 bytes_sent:28 bytes_retrans:24 segs_out:7 segs_in:3 data_segs_out:7 send 33.8kbps lastsnd:74641 lastrcv:308618 lastack:307585 pacing_rate 676kbps delivered:1 app_limited busy:269672ms unacked:1 retrans:1/7 lost:1 rcv_space:57076 rcv_ssthresh:57076 minrtt:2000 snd_wnd:59264# 從 6 s 開始翻倍,6、12、24、48、96,在傳完 5 次后超時時間固定為 120s。最終重傳完 11 次后,總時間超過了 900 多s,系統終止連接
$ while true; do sudo netstat -anpo | grep 9527 | grep -v LISTEN; sleep 1; done
tcp        0      0 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           off (0.00/0/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (3.98/0/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (2.96/0/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (1.94/0/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (0.92/0/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (0.00/0/0)
....
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (5.24/0/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (4.22/0/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (3.20/0/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (2.17/0/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (1.15/0/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (0.13/0/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (11.25/1/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (23.27/2/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (47.80/3/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (95.36/4/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (119.48/5/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (119.48/11/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (2.70/11/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (1.68/11/0)
...
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (0.00/11/0)
tcp        0      4 172.19.0.3:9527         172.19.0.6:39054        ESTABLISHED 1910324/nc           on (0.00/11/0)

抓包結果如下:

快速重傳

可以看到依賴于 RTO 的重傳會因為 TCP_RTO_MIN 的影響,導致重傳超時時間很長,效率很低。為此 RFC 5681 中提出了快速重傳(Fast Retransmit),該算法不以時間作為重傳依據,而是按照收到的重復 ACK 來判斷是否需要重傳。

RFC 規定,當接收方收到的包亂序時,要立即響應一個 Duplicate ACK,比如有 1、2、3、4、5 共5個包,在收到 1 后接收方 ACK 為 2,表示希望接下來收到 2 號包,但此時如果收到了 3、4、5 號包,此時接收方需要立即響應 duplicate ACK 給發送方。

RFC 規定發送方在收到 3 個 Duplicate ACK 后,會立即重傳,這樣判斷的依據是,有兩種情況會導致接收方收到的包亂序:亂序丟包

  • 如果是亂序,接收方通常會稍后收到預期的包,比如在收到 3 后才收到 2 號包,此時發送方一般只會收到 1 ~ 2 次 Duplicate ACK。

  • 如果是丟包,就會導致接收方多次響應 Duplicate ACK,此時發送方就可以認為是數據包丟失從而引發進行快速重傳。

下面使用 scapy 來模擬快速重傳的過程。代碼如下:

  • 服務端程序
import socket
import time def start_server(host, port, backlog):server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind((host, port))server.listen(backlog)client, _ = server.accept()client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # 禁用 Nagle 算法client.sendall(b"a" * 1460)time.sleep(0.01) # 避免協議棧合并包的方式,不嚴謹但是湊合能工作client.sendall(b"b" * 1460)time.sleep(0.01)client.sendall(b"c" * 1460)time.sleep(0.01)client.sendall(b"d" * 1460)time.sleep(0.01)client.sendall(b"e" * 1460)time.sleep(0.01)client.sendall(b"f" * 1460)time.sleep(0.01)client.sendall(b"g" * 1460)time.sleep(10000)if __name__ == '__main__':start_server('172.19.0.3', 9527, 8)
  • 客戶端程序
import threading
import time
from scapy.all import *
from scapy.layers.inet import *class ACKDataThread(threading.Thread):def __init__(self):super().__init__()self.first_data_ack_seq = 0def run(self):def packet_callback(packet):ip = IP(dst="172.19.0.3")resp_tcp = packet[TCP]# 收到第二次握手包if 'SA' in str(resp_tcp.flags):recv_seq = resp_tcp.seqrecv_ack = resp_tcp.ackprint(f"received SYN, seq={recv_seq}, ACK={recv_ack}")send_ack = recv_seq + 1tcp = TCP(sport=9528, dport=9527, flags='A', seq=2, ack=send_ack)print(f"send ACK={send_ack}")# 第三次握手send(ip/tcp)return# 收到數據包elif resp_tcp.payload:print("-" * 50)print(f"Received TCP packet")print(f"Flags: {resp_tcp.flags}")print(f"Sequence: {resp_tcp.seq}")print(f"ACK: {resp_tcp.ack}")print(f"Payload: {resp_tcp.load}")# send_ack = resp_tcp.seq + len(resp_tcp.load)if self.first_data_ack_seq == 0:self.first_data_ack_seq = resp_tcp.seq + len(resp_tcp.load)send_ack = self.first_data_ack_seqtcp = TCP(sport=9528, dport=9527, flags='A', seq=2, ack=send_ack)print(f"send ACK={send_ack}")# 發送 4 次重復的 ACKsend(ip/tcp)send(ip/tcp)send(ip/tcp)send(ip/tcp)interface = "eth0"  # 根據實際絡接口名稱更改sniff(iface=interface, prn=packet_callback, filter="tcp and port 9527", store=0)def main():thread = ACKDataThread()thread.start()time.sleep(1)ip = IP(dst="172.19.0.3")tcp = TCP(sport=9528, dport=9527, flags='S', seq=1, options=[('MSS', 1460)])# 第一次握手print("send SYN, seq=0")send(ip/tcp)thread.join()if __name__ == "__main__":main()

啟動程序

# vm-1
# 啟動服務端
$ python3 server.py
# 開啟抓包
$ sudo tcpdump -S -s0 -nn "tcp port 9527" -w tcp-fast-retra.pcap --print# vm-2
# 丟棄 RST 包
$ sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST --dport 9527 -j DROP# 啟動客戶端
$ python3 client.py

我們將抓包結果放到 Wireshark 中做分析,其標識了 Duplicate ACK 的包和快速重傳的包,可以看到在服務端 0.018s 發送了數據包,然后在 0.072s 進行了快速重傳,中間只差了 54ms,比 RTO 要小很多。然后在 0.285s 又進行了一次重傳,這個和之前的快速重傳包差了大約 200ms,已經是超時重傳在進行了,后續在 0.709s、1.589s 進行的重傳,時間間隔基本符合指數退避的規律。

Wireshark -> 統計 -> TCP 流圖形 -> 序列號(tcptrace)窗口中可以看到重傳的標識,其中的藍色豎線表示有包發生了重傳。

雖然 RFC 規定收到 3 個 Duplicate ACK 后才需要快速重傳,但 Linux 提供了參數 net.ipv4.tcp_reordering來控制,默認為 3,如果我們修改為 1 可以看到在收到一個 Duplicate ACK 后就會立即重傳。當然,生產環境中不建議修改這些參數。

$ sudo sysctl -w net.ipv4.tcp_reordering=1
net.ipv4.tcp_reordering = 1

SACK(Selective ACK)

SACK 選擇性是 TCP 提供的一種選擇重傳機制,允許發送方在收到亂序包時,只重傳丟失的包,而不是重傳整個窗口的數據。

圖片來自:TCP/IP Guide

SACK 需要雙方協商,在握手時需要發送方在選項中攜帶 SACK 選項,接收方在收到后會啟用 SACK 機制。在 Linux 下 由 net.ipv4.tcp_sack 參數控制。

$ sysctl net.ipv4.tcp_sack
net.ipv4.tcp_sack = 1

我們使用 nc 作為服務端,Scapy 作為客戶端來復現 SACK 的情況。

 nc -k -l 172.19.0.15  9527

客戶端代碼

#!/usr/bin/env python3
# -*- coding: utf-8 -*-import time
from scapy.all import *
from scapy.layers.inet import *def main():ip = IP(dst="172.19.0.15")myself_seq = 1tcp = TCP(sport=9528, dport=9527, flags='S', seq=myself_seq, options=[("SAckOK", '')])print("send SYN, seq=0")resp = sr1(ip/tcp, timeout=2)if not resp:print("recv timeout")returnresp_tcp = resp[TCP]if 'SA' in str(resp_tcp.flags):recv_seq = resp_tcp.seqrecv_ack = resp_tcp.ackprint(f"received SYN, seq={recv_seq}, ACK={recv_ack}")myself_seq += 1send_ack = recv_seq + 1tcp = TCP(sport=9528, dport=9527, flags='A', seq=myself_seq, ack=send_ack)print(f"send ACK={send_ack}")send(ip/tcp)# 特意注釋掉,讓發的數據有空洞# send data# payload = b"a" * 10# tcp = TCP(sport=9528, dport=9527, flags='A', seq=myself_seq, ack=send_ack)# send(ip/tcp/payload)myself_seq += 10payload = b"b" * 10tcp = TCP(sport=9528, dport=9527, flags='A', seq=myself_seq, ack=send_ack)send(ip/tcp/payload)myself_seq += 10# 特意注釋掉,讓發的數據有空洞# payload = b"c" * 10# tcp = TCP(sport=9528, dport=9527, flags='A', seq=myself_seq, ack=send_ack)# send(ip/tcp/payload)myself_seq += 10payload = b"d" * 10tcp = TCP(sport=9528, dport=9527, flags='A', seq=myself_seq, ack=send_ack)send(ip/tcp/payload)elif 'R' in str(resp_tcp.flags):print(f"received RST")else:print("received different TCP flags")time.sleep(100)if __name__ == "__main__":main()

因為是使用 Scapy 偽造的 SYN 請求,內核中是沒有 TCP 連接的,服務端的響應回來后內核會返回 RST 來終止連接。我們需要注意在客戶端機器添加 iptables 規則將 RST 包屏蔽掉。

sudo iptables -A OUTPUT -p tcp --tcp-flags RST RST -s 172.19.0.11 -j DROP

然后開啟抓包并運行客戶端程序,可以看到 SACK 相關的相關信息。

滑動窗口

TCP 在發送數據時必須保證接收端能夠正常接收數據,如果接收端已經沒有空間接收數據了,發送端應該暫停發送數據,這一機制是通過 滑動窗口(Sliding Window) 實現的。

發送端會維護一個發送窗口結構對要發送的數據進行管理,如圖所示:

圖片來自 TCP/IP Guide。

發送窗口以字節為單位管理數據,將數據分為四類:

  • #1 已經發送且已被確認的數據
  • #2 已經發送但未被確認的數據
  • #3 尚未發送但可以發送的數據(此時接收端還有空間)
  • #4 等待被發送的數據(此時接收端沒有足夠空間接收這些數據)

“黑色框”就是發送數據的窗口,當第二類的數據被確認后,它就可以向右滑動,這樣后續的數據就可以繼續發送了。

圖片來自 TCP/IP Guide

TCP 連接的窗口大小是在三次握手時確定的,相關字段和計算方式參考 # TCP 連接的建立與關閉抓包分析,這里不在贅述。

對于還存在的 TCP 連接,可以通過 ss 命令查看其 wscale,示例如下,其 wscale 為 7,則其真實的窗口大小為 window * (2 ^7)。

$ sudo ss -tip | grep -A 1 9527
ESTAB 105856 0        172.19.0.15:9527      172.19.0.11:43120 users:(("python3",pid=710678,fd=4))cubic wscale:7,7 rto:204 rtt:0.175/0.087 ato:80 mss:8448 pmtu:8500 rcvmss:8448 advmss:8448 cwnd:10 bytes_received:105856 segs_out:8 segs_in:22 data_segs_in:17 send 3.86Gbps lastsnd:2032 lastrcv:1944 lastack:1920 pacing_rate 7.72Gbps delivered:1 app_limited rcv_rtt:0.287 rcv_space:57076 rcv_ssthresh:57076 minrtt:0.175

抓包查看信息符合我們的計算:

下面我們用代碼結合抓包看下滑動窗口的工作過程。

  • 服務端代碼
import socket
import timedef start_server(host, port, backlog):server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind((host, port))# 只監聽端口,不讀取數據。server.listen(backlog)client, _ = server.accept()time.sleep(10000)if __name__ == '__main__':start_server('172.19.0.15', 9527, 8)
  • 客戶端代碼
import socket
import timedef start_client(host, port):client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)client.connect((host, port))client.setblocking(False)send_size = 0data = b"a" * 100000# 每秒發送數據while True:try:size = client.send(data)if size > 0:send_size += sizeprint(f"send size: {size}")print(f"total send size: {send_size}\n")time.sleep(1)except BlockingIOError:time.sleep(0.1)passif __name__ == '__main__':start_client('172.19.0.15', 9527)

零窗口探測

運行程序后分析抓包信息,可以看到數據在發送一段時間之后,窗口會變為 0 。tcpdump 在第 28 行輸出了 win 0,在 Wireshark 中第 28 個展示為 Zero Window,可以通過 tcp.analysis.zero_window 來過濾該類包。

發送端在收到 Zero Window 包后就停止發送數據了,為了在接收端窗口恢復正常時繼續發送數據,發送端會觸發零窗口探測(Zero Window Probe),定時發送探活包去探聽接收端的窗口大小,查看發送端的 socket 狀態可以看到啟用了 probe 計時器來計算 ZWP 探活包的發送時間。

$ while true; do sudo netstat -anpo | grep -E "Recv-Q|9527" ;echo ; sleep 1; done
Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name     TimerProto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name     TimerProto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name     Timer
tcp        0  74656 172.19.0.11:34408       172.19.0.15:9527        ESTABLISHED 493448/python3       on (0.17/0/0)Proto Recv-Q Send-Q Local Address           Foreign Address         State       PID/Program name     Timer
tcp        0  90912 172.19.0.11:34408       172.19.0.15:9527        ESTABLISHED 493448/python3       probe (0.20/0/0)

Silly Window Syndrome

上述基于滑動窗口的流控會導致所謂的 “糊涂窗口綜合征”,每當發送端檢測到接收端有一點窗口釋放出來后就立即發送數據,這會導致大量的小包傳輸,嚴重影響網絡傳輸性能。解決辦法就是避免對這類小窗口進行處理。具體方法有:

  • 對于接收端,RFC 1122 規定可用空間必須不小于 Recieve Buffer 的一半與發送方一個完整 MSS 的最小值。比如我們的 Receive Buffer 為 1024byte,而發送端的 MSS 為 600 bytes,則只有接收端的可用 buffer > Min(1024/2,600)=512 時,才會告知發送端其真實 window 大小,否則還是返回 Zero Window。

  • 對于發送端,就是大名鼎鼎的 Nagle 算法了,RFC 1122中作了說明,和 Delayed ACK 一樣也是延遲發送的思路,其規定當發送端存在未被 ACK 的數據時,其會延遲發送數據,直到其 1)收到了 ACK 或 2)待發送數據超過了 SMSS。Nagle 算法是默認打開的,并且沒有全局的開關設置,對于像 SSH 這種交互性強的場景,通常需要頻繁發送小包,此時 Nagle 算法會影響性能。可以通過設置 socket 的 TCP_NODELAY 來關閉。

我們修改下服務端程序,讓其正常接收數據,然后再次抓包分析 TCP 的傳輸過程。

import socket
import timedef start_server(host, port, backlog):server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)server.bind((host, port))server.listen(backlog)client, _ = server.accept()client.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) # 禁用 Nagle 算法while True:for i in range(5):client.recv(4096)time.sleep(1)time.sleep(10000)if __name__ == '__main__':start_server('172.19.0.15', 9527, 8)

抓包后其傳輸過程如圖:

綠色線表示的就是接收窗口的大小,黃線表示 ACK 的數據變化。可以看到數據得到確認,黃線會上漲,同時接收窗口也會增長。藍色點代表數據發送,每次接收窗口變化后數據也隨之發送。

這里可以與第一次的抓包做對比,因為服務端不會主動接受數據,因此其黃線和綠線是不變的,進而導致發送端停止發送數據。

最后筆者在抓包時也遇到了巨幀(Jumbo Frames)的問題,可以看到很多數據包的大小明顯超過了 MTU 8500 的限制。

這是 Linux 的 GRO/GSO/TSO 機制導致的,它們主要是為了優化數據傳輸的性能,其功能分別是:

名稱
全稱
方向層級用處
TSOTCP Segmentation Offload發送NIC(網卡)讓網卡把大 TCP 包拆小包
GSOGeneric Segmentation Offload發送內核協議棧讓內核暫不拆包,延遲到驅動層
GROGeneric Receive Offload接收內核協議棧把多個小包合并成大包再交給協議棧處理
因為 MTU 的原因,在發送數據時對于較大的數據包通常需進行分片操作,可以看到 TSO/GSO 的作用是將分片操作延遲到網卡驅動層;而 GRO 則是反過來,在收到包時將其合并成大包后再交給系統的協議棧處理,這樣可以降低系統的開銷。

三種機制都是默認開啟的,可以通過如下命令查看:

$ sudo ethtool -k eth0 | grep -E "generic-segmentation-offload|generic-receive-offload|tcp-segmentation-offload"
tcp-segmentation-offload: on
generic-segmentation-offload: on
generic-receive-offload: on

上述三種機制是在網卡或者內核驅動層生效的,比抓包更加的底層,因此會導致我們抓到巨幀。如果需要可以臨時關閉,命令如下:

$ sudo ethtool -K eth0 gso off$ sudo ethtool -K eth0 gro off$ sudo ethtool -K eth0 tso off

擁塞控制

上面提到的滑動窗口指的是接收方的接收窗口(Receiver Window),用來解決發送端和接收端的速率匹配問題,保證發送端的發送速度不會超過接收方的接收速度。除此之外,數據的發送速度受到網絡環境的影響,如同我們發送獲取到港口出口,為了及時發出去,除了港口的吞吐速度,還要考慮路上是不是堵車。

擁塞控制作為 TCP 協議最復雜的部分,相關算法層出不窮,到今天也在不斷研究演進中。這里我們只關注最主要四個傳統算法

四種傳統算法

擁塞控制作為 TCP 協議最復雜的部分,相關算法層出不窮,到今天也在不斷研究演進中。這里我們只關注最主要四個傳統算法:

  • 1988年,TCP-Tahoe 提出了 慢啟動(Slow start)、擁塞避免(Congestion Avoidance)、快速重傳(fast retransmit)。
  • 1990 年 TCP Reno 在 Tahoe 的基礎上增加快速恢復(Fast Recovery)。
慢啟動 Slow start

顧名思義,慢啟動的意思就是在 TCP 開始發送數據時,一點一點的逐步提高發送速度,不要一下子全力發送,把整個網絡給占滿,如果我們剛上高速時要逐步加速匯入主干道。

其實現主要依賴 cwnd(Congestion window,擁塞窗口),Linux 3.0 以后默認為 10 且不可更改。cwnd 表示的是 TCP 在收到 ACK 時最多能夠發送的包的個數。也就是說最開始 Linux 最多發送 10 個數據包,最大數據量為 MSS * 10。

其工作過程如下:

  • cwnd 初始化為 10。
  • 每收到 1 個 ACK, 則線性增加 cwnd++
  • 每超過一個RTT,則指數增加 cwnd 翻倍
  • 到達 ssthresh(slow start threshold)上限后,進入擁塞避免算法。
擁塞避免(Congestion Avoidance)

上面提到了 ssthresh(slow start threshold),這是慢啟動的上限。超過這個界限后,TCP 會采用擁塞避免算法,將 cwnd 改為線性增長,慢慢的找到適合網絡的最佳值。具體方式是:

  • 收到一個ACK時,cwnd = cwnd + 1/cwnd。
  • 每過一個RTT時,cwnd = cwnd + 1。

擁塞時的處理

目前提到的算法都是基于丟包來判斷網絡是否堵塞的。當丟包 TCP 會進行重傳,此時有兩種情況:

  1. RTO 超時重傳

TCP 會認為這是比較嚴重的網絡問題,此時會將:
- sshthresh 降為 cwnd /2
- cnwd 重置為 1
- 進入慢啟動狀態

可以看到超時重傳會極大的影響 TCP 的傳輸性能。

  1. 快速重傳

TCP Tahoe的實現和上面的超時重傳一樣,TCP Reno 則提出了不同的實現:

  • cwnd = cwnd /2
  • sshthresh = cwnd
  • 進入快速恢復算法
快速恢復(Fast Recovery)

快速算法是基于快速重傳來實現的,當收到 3 個duplicated ACK 時,它認為網絡沒有想象的那么糟糕,沒必要像超時重傳那樣降 cwnd 粗暴的重置為 1。其在 cwnd 降為 cwnd /2,sshthresh = cwnd 后:

    • cwnd = sshthresh + 3 * MSS (3的意思是確認有3個數據包被收到了)
  • 重傳 duplicated ACK 對應的數據包

Cubic 算法

Linux 內核在 2.6.19 后默認的擁塞控制是 CUBIC 算法,它使用三次函數作為其擁塞窗口的算法,并且使用函數拐點作為擁塞窗口的設置值,具體細節可以參考 Cubic 論文。Linux 中通過如下幾個參數來設置擁塞算法:

$ sysctl -a | grep congestion# 允許使用的擁塞算法
net.ipv4.tcp_allowed_congestion_control = reno cubic# 內核中已經加載可用的擁塞算法
net.ipv4.tcp_available_congestion_control = reno cubic# 當前默認的擁塞算法
net.ipv4.tcp_congestion_control = cubic

我們創建一個 4GB 大小的文件在兩臺機器之間傳輸。

# 服務端,收到的數據全部丟棄
$ nc -k -l 172.19.0.15  9527 > /dev/null# 客戶端,創建 4GB 的文件并傳輸
$ dd if=/dev/zero of=testfile bs=1M count=4096$ nc 172.19.0.15 9527 < testfile

執行后抓包如下,可以看到傳輸過程還是比較絲滑的,cwnd 基本維持在 40 左右。

我們使用 tc 在客戶端機器添加一定的丟包率

$ sudo tc qdisc replace dev eth0 root netem loss 5%

再次執行請求后抓包,可以看到傳輸速度從 21M 降到了 17M,看 tcptrace 會發現很多紅色線代表重傳。請求過程中查看 cwnd 會發現因為丟包會被不斷重置為 1,從而影響發送效率。

BBR 算法

BBR 算法是近些年研究最為活躍的擁塞控制算法,其發送速率控制完全不在意丟包,自己會不斷探測整個傳輸鏈路的帶寬和時延,最終讓發送數據穩定在帶寬時延積。因此相比于上述算法,理論上 BBR 算法的傳輸性能會更有。

Linux 內核從 4.9 開始就支持 BBR 算法了,我們的內核版本是 5.15.0-139-generic,因此是支持的只需要啟用下即可,方式如下:

# 檢查內核配置文件是否支持BBR,如果是 y 說明已經內置,可以直接啟用;如果是 m 說明是基于模塊存在,需要加載模塊;如果沒有需要更新內核。
$ sudo cat /boot/config-$(uname -r) | grep CONFIG_TCP_CONG_BBR
CONFIG_TCP_CONG_BBR=m# BBR 需要配合 fq 調度器使用,看是否已支持,輸出是 m 說明支持。
# ubuntu @ vm-02 in ~ [10:02:06]
$ sudo cat /boot/config-$(uname -r) | grep CONFIG_NET_SCH_FQ
CONFIG_NET_SCH_FQ_CODEL=m
CONFIG_NET_SCH_FQ=m
CONFIG_NET_SCH_FQ_PIE=m# 加載 bbr 模塊
$ sudo modprobe tcp_bbr# 查看可用算法
$ sysctl net.ipv4.tcp_available_congestion_control
net.ipv4.tcp_available_congestion_control = reno cubic bbr

bbr 算法可用后,修改 tcp_congestion_control 和 qdisc 配置即可啟用 BBR:

$ sysctl -w net.ipv4.tcp_congestion_control=bbr net.core.default_qdisc=fq
net.core.default_qdisc=fq
net.ipv4.tcp_congestion_control=bbr

啟用 BBR 算法后我們再次執行上述文件傳輸并抓包,在設置 5% 的丟包率前后其傳輸性能沒有較大差異,均為 40M/s 左右。


cwnd 也沒有出現重置為 1 的情況,實驗時一直穩定在 36。

# 5% 丟包率啟用 BBR 算法時的 cwnd 變化情況。
$ while true; do ss -i | grep -A 1 9527; sleep 1; done
tcp   ESTAB  0      1969152                  172.19.0.11:45832         172.19.0.15:9527bbr wscale:7,7 rto:204 backoff:1 rtt:1.444/0.949 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:36 ssthresh:24 bytes_sent:1447680 bytes_retrans:25344 bytes_acked:1422337 segs_out:184 segs_in:57 data_segs_out:181 bbr:(bw:472Mbps,mrtt:0.105,pacing_gain:1.25,cwnd_gain:2) send 1.68Gbps lastsnd:412 lastrcv:676 lastack:200 pacing_rate 584Mbps delivery_rate 73Mbps delivered:179 busy:672ms rwnd_limited:668ms(99.4%) retrans:0/3 rcv_space:57088 rcv_ssthresh:57088 notsent:1969152 minrtt:0.105
tcp   ESTAB  0      1969152                  172.19.0.11:45832         172.19.0.15:9527bbr wscale:7,7 rto:204 backoff:2 rtt:1.444/0.949 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:36 ssthresh:24 bytes_sent:1447680 bytes_retrans:25344 bytes_acked:1422337 segs_out:185 segs_in:57 data_segs_out:181 bbr:(bw:472Mbps,mrtt:0.105,pacing_gain:1.25,cwnd_gain:2) send 1.68Gbps lastsnd:1416 lastrcv:1680 lastack:1204 pacing_rate 584Mbps delivery_rate 73Mbps delivered:179 busy:1676ms rwnd_limited:1672ms(99.8%) retrans:0/3 rcv_space:57088 rcv_ssthresh:57088 notsent:1969152 minrtt:0.105
tcp   ESTAB  0      1969152                  172.19.0.11:45832         172.19.0.15:9527bbr wscale:7,7 rto:204 backoff:3 rtt:1.444/0.949 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:36 ssthresh:24 bytes_sent:1447680 bytes_retrans:25344 bytes_acked:1422337 segs_out:186 segs_in:58 data_segs_out:181 bbr:(bw:472Mbps,mrtt:0.105,pacing_gain:1.25,cwnd_gain:2) send 1.68Gbps lastsnd:2420 lastrcv:2684 lastack:948 pacing_rate 584Mbps delivery_rate 73Mbps delivered:179 busy:2680ms rwnd_limited:2676ms(99.9%) retrans:0/3 rcv_space:57088 rcv_ssthresh:57088 notsent:1969152 minrtt:0.105
tcp   ESTAB  0      1969152                  172.19.0.11:45832         172.19.0.15:9527bbr wscale:7,7 rto:204 backoff:4 rtt:1.444/0.949 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:36 ssthresh:24 bytes_sent:1447680 bytes_retrans:25344 bytes_acked:1422337 segs_out:187 segs_in:59 data_segs_out:181 bbr:(bw:472Mbps,mrtt:0.105,pacing_gain:1.25,cwnd_gain:2) send 1.68Gbps lastsnd:3424 lastrcv:3688 lastack:288 pacing_rate 584Mbps delivery_rate 73Mbps delivered:179 busy:3684ms rwnd_limited:3680ms(99.9%) retrans:0/3 rcv_space:57088 rcv_ssthresh:57088 notsent:1969152 minrtt:0.105
tcp   ESTAB  0      1969152                  172.19.0.11:45832         172.19.0.15:9527bbr wscale:7,7 rto:204 backoff:4 rtt:1.444/0.949 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:36 ssthresh:24 bytes_sent:1447680 bytes_retrans:25344 bytes_acked:1422337 segs_out:187 segs_in:59 data_segs_out:181 bbr:(bw:472Mbps,mrtt:0.105,pacing_gain:1.25,cwnd_gain:2) send 1.68Gbps lastsnd:4432 lastrcv:4696 lastack:1296 pacing_rate 584Mbps delivery_rate 73Mbps delivered:179 busy:4692ms rwnd_limited:4688ms(99.9%) retrans:0/3 rcv_space:57088 rcv_ssthresh:57088 notsent:1969152 minrtt:0.105
tcp   ESTAB  0      1969152                  172.19.0.11:45832         172.19.0.15:9527bbr wscale:7,7 rto:204 backoff:4 rtt:1.444/0.949 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:36 ssthresh:24 bytes_sent:1447680 bytes_retrans:25344 bytes_acked:1422337 segs_out:187 segs_in:59 data_segs_out:181 bbr:(bw:472Mbps,mrtt:0.105,pacing_gain:1.25,cwnd_gain:2) send 1.68Gbps lastsnd:5436 lastrcv:5700 lastack:2300 pacing_rate 584Mbps delivery_rate 73Mbps delivered:179 busy:5696ms rwnd_limited:5692ms(99.9%) retrans:0/3 rcv_space:57088 rcv_ssthresh:57088 notsent:1969152 minrtt:0.105
wntcp   ESTAB  0      1969152                  172.19.0.11:45832         172.19.0.15:9527bbr wscale:7,7 rto:204 backoff:4 rtt:1.444/0.949 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:36 ssthresh:24 bytes_sent:1447680 bytes_retrans:25344 bytes_acked:1422337 segs_out:187 segs_in:59 data_segs_out:181 bbr:(bw:472Mbps,mrtt:0.105,pacing_gain:1.25,cwnd_gain:2) send 1.68Gbps lastsnd:6440 lastrcv:6704 lastack:3304 pacing_rate 584Mbps delivery_rate 73Mbps delivered:179 busy:6700ms rwnd_limited:6696ms(99.9%) retrans:0/3 rcv_space:57088 rcv_ssthresh:57088 notsent:1969152 minrtt:0.105
dtcp   ESTAB  0      1969152                  172.19.0.11:45832         172.19.0.15:9527bbr wscale:7,7 rto:204 backoff:5 rtt:1.444/0.949 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:36 ssthresh:24 bytes_sent:1447680 bytes_retrans:25344 bytes_acked:1422337 segs_out:188 segs_in:60 data_segs_out:181 bbr:(bw:472Mbps,mrtt:0.105,pacing_gain:1.25,cwnd_gain:2) send 1.68Gbps lastsnd:7448 lastrcv:7712 lastack:888 pacing_rate 584Mbps delivery_rate 73Mbps delivered:179 busy:7708ms rwnd_limited:7704ms(99.9%) retrans:0/3 rcv_space:57088 rcv_ssthresh:57088 notsent:1969152 minrtt:0.105
tcp   ESTAB  0      1969152                  172.19.0.11:45832         172.19.0.15:9527bbr wscale:7,7 rto:204 backoff:5 rtt:1.444/0.949 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:36 ssthresh:24 bytes_sent:1447680 bytes_retrans:25344 bytes_acked:1422337 segs_out:188 segs_in:60 data_segs_out:181 bbr:(bw:472Mbps,mrtt:0.105,pacing_gain:1.25,cwnd_gain:2) send 1.68Gbps lastsnd:8452 lastrcv:8716 lastack:1892 pacing_rate 584Mbps delivery_rate 73Mbps delivered:179 busy:8712ms rwnd_limited:8708ms(100.0%) retrans:0/3 rcv_space:57088 rcv_ssthresh:57088 notsent:1969152 minrtt:0.105
tcp   ESTAB  0      1969152                  172.19.0.11:45832         172.19.0.15:9527bbr wscale:7,7 rto:204 backoff:5 rtt:1.444/0.949 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:36 ssthresh:24 bytes_sent:1447680 bytes_retrans:25344 bytes_acked:1422337 segs_out:188 segs_in:60 data_segs_out:181 bbr:(bw:472Mbps,mrtt:0.105,pacing_gain:1.25,cwnd_gain:2) send 1.68Gbps lastsnd:9456 lastrcv:9720 lastack:2896 pacing_rate 584Mbps delivery_rate 73Mbps delivered:179 busy:9716ms rwnd_limited:9712ms(100.0%) retrans:0/3 rcv_space:57088 rcv_ssthresh:57088 notsent:1969152 minrtt:0.105
tcp   ESTAB  0      1969152                  172.19.0.11:45832         172.19.0.15:9527bbr wscale:7,7 rto:204 backoff:5 rtt:1.444/0.949 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:36 ssthresh:24 bytes_sent:1447680 bytes_retrans:25344 bytes_acked:1422337 segs_out:188 segs_in:60 data_segs_out:181 bbr:(bw:472Mbps,mrtt:0.105,pacing_gain:1.25,cwnd_gain:2) send 1.68Gbps lastsnd:10460 lastrcv:10724 lastack:3900 pacing_rate 584Mbps delivery_rate 73Mbps delivered:179 busy:10720ms rwnd_limited:10716ms(100.0%) retrans:0/3 rcv_space:57088 rcv_ssthresh:57088 notsent:1969152 minrtt:0.105
tcp   ESTAB  0      1969152                  172.19.0.11:45832         172.19.0.15:9527bbr wscale:7,7 rto:204 backoff:5 rtt:1.444/0.949 mss:8448 pmtu:8500 rcvmss:536 advmss:8448 cwnd:36 ssthresh:24 bytes_sent:1447680 bytes_retrans:25344 bytes_acked:1422337 segs_out:188 segs_in:60 data_segs_out:181 bbr:(bw:472Mbps,mrtt:0.105,pacing_gain:1.25,cwnd_gain:2) send 1.68Gbps lastsnd:11468 lastrcv:11732 lastack:4908 pacing_rate 584Mbps delivery_rate 73Mbps delivered:179 busy:11728ms rwnd_limited:11724ms(100.0%) retrans:0/3 rcv_space:57088 rcv_ssthresh:57088 notsent:1969152 minrtt:0.105

筆者這里只用了兩臺內網機器做實驗,理論上距離更遠的傳輸路徑,BBR 更好用,鑒于篇幅這里不再過多贅述,挖個坑后面在專門寫篇 BBR 相關的實驗。關于 BBR 更詳細的論文可以參考其 論文和Google 的 Github 項目。

總結

本篇實驗基本將 TCP 數據傳輸遇到的點都做了涉獵,我覺得初學 TCP 的小伙伴最好都從類似的實驗開始,動手做一遍后再去讀理論性強的書籍和 RFC 資料,學起來會更加事半功倍。

筆者在做完TCP 連接的建立與關閉抓包分析和本篇實驗后將 《TCP/IP 詳解(英文版)》的 TCP 章節又重讀了一遍,整個閱讀體驗和收獲和之前硬啃完全不一樣。初讀時更像是一種填鴨式的硬啃,啃完過段時間也就忘了。做完實驗后重讀時,整個閱讀體驗類似有點品讀的意思,讀到相關章節之前都能回想起實驗時的場景以及相關的知識點,大腦會自動的與書中內容做對比,查缺補漏,校對細節,從而構建更堅固的理解和記憶,這樣讀下來的收獲是填鴨式閱讀遠遠不能比的。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/87764.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/87764.shtml
英文地址,請注明出處:http://en.pswp.cn/web/87764.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

python 保存二維數組到本地

Python中保存二維數組有多種方法&#xff0c;以下是常用的幾種方式&#xff1a;1. 使用NumPy&#xff08;推薦&#xff09;import numpy as np# 創建二維數組 arr np.array([[1, 2, 3], [4, 5, 6], [7, 8, 9]])# 保存為.npy文件&#xff08;NumPy專用格式&#xff09; np.save…

LIN總線通訊中從節點波特率同步原理

波特率同步原理&#xff1a;從節點如何通過0x55校準時鐘&#xff1f; 一、同步場的核心作用&#xff1a;統一“時間標尺” 在LIN總線中&#xff0c;主節點與從節點各自擁有獨立的時鐘源&#xff08;如MCU內部RC振蕩器&#xff09;&#xff0c;但由于制造工藝差異&#xff0c;…

【Unity筆記02】訂閱事件-自動開門

流程 當玩家移動到觸發區域的時候&#xff0c;門自動打開 事件系統 using System; using System.Collections; using System.Collections.Generic; using UnityEngine;public class EventSystem : MonoBehaviour {public static EventSystem Instance { get; private set; }…

控制臺字符動畫

旋轉的立方體 #include <cstdint> #include <cstdio> #include <iostream> #include <cstring> #include <cmath> #include <cstdlib> #include <ctime> #include <thread> using namespace std;float angleX .0f; float a…

基于 PyTorch 的貓狗圖像分類實戰

基于 PyTorch 的貓狗圖像分類實戰 項目背景簡介 深度學習框架 PyTorch 因其動態計算圖和靈活易用性&#xff0c;被廣泛應用于圖像分類等計算機視覺任務。在入門計算機視覺領域時&#xff0c;常常以手寫數字識別&#xff08;MNIST&#xff09;作為 “Hello World”&#xff0c…

SwiftUI 7(iOS 26 / iPadOS 26)中玻璃化標簽頁的全新玩法

&#x1f378; Liquid Glass 登場&#xff1a;界面設計煥然一新 WWDC25 可謂驚喜連連&#xff0c;其中最引人矚目的變革之一&#xff0c;莫過于蘋果推出的全新跨平臺設計語言 —— Liquid Glass&#xff08;液態玻璃&#xff09;。這一設計風格涵蓋了從按鈕到導航欄&#xff0…

PDF處理控件Spire.PDF教程:在Java中讀取PDF,提取文本、圖片和表格

在數據驅動的現代開發中&#xff0c;高效處理 PDF 文檔已成為 Java 開發者不可或缺的核心能力。無論是處理各類發票掃描件、業務分析報告&#xff0c;還是包含豐富圖表的技術文檔&#xff0c;掌握 Java 版的 PDF 解析技術都將大幅提升數據處理效率&#xff0c;充分釋放文檔中的…

跨平臺游戲引擎 Axmol-2.7.0 發布

Axmol 2.7.0 版本是一個以錯誤修復和功能改進為主的次要LTS長期支持版本 &#x1f64f;感謝所有貢獻者及財務贊助者&#xff1a;scorewarrior、peterkharitonov、duong、thienphuoc、bingsoo、asnagni、paulocoutinhox 重大變更 Android Studio 最低版本要求升級至 2025.1.1…

XML 筆記

<image src"hue.gif" width"100" height"auto" align"left"/> <br/> 換行 在 XML 中&#xff0c;<![CDATA[ 和 ]]> 用于定義一個 CDATA 節&#xff08;Character Data Section&#xff09;。CDATA 節是用于將一段…

Python實現優雅的目錄結構打印工具

Python實現優雅的目錄結構打印工具 在軟件開發、系統管理和日常工作中&#xff0c;我們經常需要查看和分析目錄結構。 工具功能概述 這個DirectoryPrinter類提供了以下功能&#xff1a; 遞歸打印目錄結構可配置是否顯示隱藏文件可設置最大遞歸深度自定義縮進和文件/文件夾符…

【Python】文件打開:with open具體解析

示例 # 使用 with 語句打開文件并讀取內容 with open(pi.txt, r) as file_object:contents file_object.read()print(contents) # 文件在代碼塊結束后自動關閉with 解析 with 是 Python 中的上下文管理器語法&#xff0c;用于確保某個操作完成后自動執行清理操作。它常用于文…

Acrel-1000系列分布式光伏監控系統在湖北荊門一馬光彩大市場屋頂光伏發電項目中應用

摘 要&#xff1a;分布式光伏發電能夠對日益嚴重的環境壓力起到有效緩解作用,在當前對環境保護需求越來越大情況下,發電行業在發展中不但要提升發電效率,同時也需要降低成本。分布式光伏發電主要是利用風能和太陽能等可再生清潔能源進行發電,對于空氣質量具有改善效果,和傳統發…

CentOS-6與CentOS-7的網絡配置IP設置方式對比 筆記250706

CentOS-6與CentOS-7的網絡配置IP設置方式對比 筆記250706 1?? 參考 1 CentOS-6 與 CentOS-7 的網絡配置IP設置方式對比 CentOS 6 和 CentOS 7 在網絡配置上存在顯著差異&#xff0c;主要體現在配置文件結構、管理工具、服務機制和命令集等方面。以下是兩者的核心對比&#x…

【網絡系列】HTTP 429 狀態碼

博客目錄 HTTP 429 狀態碼的定義與背景產生 429 錯誤的常見場景1. API 速率限制觸發2. 網絡爬蟲行為被檢測3. 分布式拒絕服務(DDoS)防護4. 用戶/IP 特定限流策略5. 應用程序邏輯錯誤 深入解讀 429 響應的關鍵頭部信息Retry-After 頭部X-RateLimit 系列頭部RateLimit 標準化頭部…

C++無鎖數據結構:CAS(Compare-and-Swap)

在高并發場景下&#xff0c;傳統鎖機制帶來的線程阻塞和上下文切換開銷成為性能瓶頸。無鎖數據結構通過原子操作實現線程安全&#xff0c;避免了鎖的使用&#xff0c;成為高性能系統的關鍵技術。本文將深入探討C中基于CAS&#xff08;Compare-and-Swap&#xff09;的無鎖數據結…

【數字圖像處理】

數字圖像處理 緒論1. 數字圖像處理基本概念2. 數字圖像處理系統的組成3. 數字圖像處理技術研究的內容4. 數字圖像處理技術的應用領域5. 圖像處理技術涉及的學科領域 圖像處理基礎1. 電磁波譜與可見光譜2. 人眼的亮度視覺特性3. 圖像的表示4. 空間分辨率和灰度級分辨率5. 像素間…

linux chrome瀏覽器打不開了

報錯信息 通過terminal執行google-chrome [12714:12714:0706/223620.723519:ERROR:chrome/browser/process_singleton_posix.cc:358] The profile appears to be in use by another Google Chrome process (54949) on another computer (192.168.0.17). Chrome has locked t…

Python:模塊

一、Python模塊基礎概念 1. 什么是Python模塊&#xff1f; 在 Python 中&#xff0c;模塊&#xff08;Module&#xff09; 是一個包含 Python 代碼的文件&#xff08;擴展名為 .py&#xff09;&#xff0c;用于組織代碼、實現功能復用和命名空間管理。模塊可以包含變量、函數…

C 語言指針與作用域詳解

一、指針基礎概念 &#xff08;一&#xff09;指針的本質 指針是 C 語言中一個重要的概念&#xff0c;其本質是內存地址。在計算機內存中&#xff0c;每個字節都有唯一的編號&#xff0c;這個編號就是我們所說的內存地址&#xff0c;而指針變量就是用于存儲這些內存地址的變量…

解鎖阿里云ACK:開啟Kubernetes容器化應用新時代

引言&#xff1a;云原生時代下的 ACK 在當今數字化飛速發展的時代&#xff0c;云原生技術正以前所未有的速度改變著軟件開發和部署的格局。隨著企業對應用敏捷性、彈性擴展以及成本優化的需求日益增長&#xff0c;云原生已成為眾多企業實現數字化轉型的關鍵路徑。在云原生的技…