TCP(六) -- 重傳與確認

一:摘要概述

TCP是可靠的傳輸層協議,網絡層採用不靠譜的IP協議導致其自身必須保證數據傳輸的可靠。其中最終要的就是將丟失的數據包進行重傳,當數據包發送後TCP就會開啓計時器,當計時器達到閾值且發送的數據包未被數據接收方確認就會重新傳遞丟失的數據包。當然,重傳的前提就是需要確認機制。本文將詳細介紹重傳與確認,也會涉及到快速重傳與延遲確認的概念

二:超時重傳模擬

如下編輯一段packetdrill腳本,將ACK響應部分註釋,當數據發送方無法獲取響應時就會進行數據重傳。然後使用tcpdump抓包保存文件,Wireshark進行抓包分析。腳本中就是簡單經典的三次握手環節模擬以及數據傳輸

// 三次握手
0   socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0  setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0  bind(3, ..., ...) = 0
+0  listen(3, 1) = 0
+0  < S 0:0(0) win 4000 <mss 1000>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 4000
+0  accept(3, ..., ...) = 4
// 數據發送
+0  write(4, ..., 1000) = 1000
// 註釋掉數據接收方ACK響應
// +.1 < . 1:1(0) ack 1001 win 1000
+0 `sleep 1000000`
// 抓包命令
tcpdump -i any -nn -vv -w /home/retry.pacp port 8080

三:超時重傳分析

首先關心的就是超時重傳次數,這個次數由參數/pro/sys/net/ipv4/tcp_retries2控制,一般默認數值爲15。使用命令查看:

[root@zsl home]# cat /proc/sys/net/ipv4/tcp_retries2
15

其次關係的應該就是重傳時間,TCP超時重傳使用指數避讓策略,可以看Wireshark分析圖中的紅框部分。3-6-12-24......
在這裏插入圖片描述
還有一點必須要糾正,重傳的次數並不會由tcp_retries2完全控制,底層函數實現僅僅只是將其作爲參數參考,還會隨着RTO波動動態調整

四:快速重傳模擬

與超時重傳一致編寫packetdrill腳本,tcpdump轉包,Wireshark分析

--tolerance_usecs=100000
  // 常規操作:初始化
0  socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
+0 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
+0 bind(3, ..., ...) = 0
+0 listen(3, 1) = 0
+0  < S 0:0(0) win 32792 <mss 1000,sackOK,nop,nop,nop,wscale 7>
+0  > S. 0:0(0) ack 1 <...>
+.1 < . 1:1(0) ack 1 win 257
+0 accept(3, ... , ...) = 4
// 往客戶端寫 5000 字節數據
+0.1 write(4, ..., 5000) = 5000
+.1 < . 1:1(0) ack 1001 win 257 <sack 1:1001,nop,nop>
 // 三次重複 ack
+0  < . 1:1(0) ack 1001 win 257 <sack 1:1001 2001:3001,nop,nop>
+0  < . 1:1(0) ack 1001 win 257 <sack 1:1001 2001:4001,nop,nop>
+0  < . 1:1(0) ack 1001 win 257 <sack 1:1001 2001:5001,nop,nop>
// 回覆確認包,讓服務端不再重試
+.1 < . 1:1(0) ack 5001 win 257
+0 `sleep 1000000`
// 抓包命令
tcpdump -i any -nn -vv -w /home/sack.pacp port 8080

五:快速重傳分析

在這裏插入圖片描述
首先如圖看抓包的Wireshark展示,其過程如下:

  • 1-3:三次握手
  • 4-8:5000字節數據包傳輸
  • 9-12:數據接收方確認
  • 13:執行快速重傳

其中最主要的過程就在於9-12這四次數據接收方的確認,數據發送方發送了0-1000、1001-2001、2001-3001、3001-4001、4001-5001的五個數據包。但是確認的時候可以看到僅僅只是確認了四個數據包,打開其中的10、11、12查看詳情,其中10如下圖所示。ACK的序列號都是1001,但是在Options中攜帶了已經接收到的數據包序列號
在這裏插入圖片描述
其過程如下描述:

  • 9號:兄弟俺已經接收到0-1001的數據包了
  • 10號:兄弟俺接收到最大連續序列號1001的數據包,但是2001-3001也到了
  • 11號:兄弟俺接收到最大連續序列號1001的數據包,但是2001-4001也到了
  • 12號:兄弟俺接收到最大連續序列號1001的數據包,但是2001-5001也到了
    在這裏插入圖片描述
    圖片來源於張師傅掘金小冊:超時重傳、快速重傳與SACK,已經取得過張師傅同意,有興趣的朋友購買小冊真的物有所值。快速重傳的條件就是當數據發送方接收到3個及以上相同的ACK數據包時就會判定數據包丟失,即使超時重傳計時器未達到時間也會進行數據重傳

六:延遲確認

正常的思路是當數據接收方接收到數據包後確認無誤就需要發送ACK確認,但是實際上TCP會採用延遲確認的策略減少性能開銷。當接收到數據後會稍微等待下看是否有數據包需要返回,如果有數據包返回就會順帶一起進行ACK確認,當然若等待過程中沒有數據包傳輸最後也會單獨進行ACK確認,這就是延遲確認。延遲確認有以下場景不適用:

// tcp.input.c文件源碼
static void __tcp_ack_snd_check(struct sock *sk, int ofo_possible)
{
	struct tcp_sock *tp = tcp_sk(sk);

	    /* More than one full frame received... */
	if (((tp->rcv_nxt - tp->rcv_wup) > tp->ack.rcv_mss
	     /* ... and right edge of window advances far enough.
	      * (tcp_recvmsg() will send ACK otherwise). Or...
	      */
	     && __tcp_select_window(sk) >= tp->rcv_wnd) ||
	    /* We ACK each frame or... */
	    tcp_in_quickack_mode(tp) ||
	    /* We have out of order data. */
	    (ofo_possible &&
	     skb_peek(&tp->out_of_order_queue))) {
		/* Then ack it now */
		tcp_send_ack(sk);
	} else {
		/* Else, send delayed ack. */
		tcp_send_delayed_ack(sk);
	}
}
  • 如果接收到了大於一個frame 的報文,且需要調整窗口大小
  • 處於 quickack 模式(tcp_in_quickack_mode)
  • 收到亂序包(We have out of order data.)

如下使用packetdrill腳本模擬場景

--tolerance_usecs=100000
0.000 socket(..., SOCK_STREAM, IPPROTO_TCP) = 3
0.000 setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
0.000 bind(3, ..., ...) = 0
0.000 listen(3, 1) = 0

0.000 < S 0:0(0) win 32792 <mss 1000, sackOK, nop, nop, nop, wscale 7>
0.000 > S. 0:0(0) ack 1 <...>

0.000 < . 1:1(0) ack 1 win 257

0.000 accept(3, ..., ...) = 4

+ 0 setsockopt(4, SOL_TCP, TCP_NODELAY, [1], 4) = 0

// 模擬往服務端寫入 HTTP 頭部: POST / HTTP/1.1
+0 < P. 1:11(10) ack 1 win 257

// 模擬往服務端寫入 HTTP 請求 body: {"id": 1314}
+0 < P. 11:26(15) ack 1 win 257

// 往 fd 爲4 的 模擬服務器返回 HTTP response {}
+ 0 write(4, ..., 100) = 100


// 第二次模擬往服務端寫入 HTTP 頭部: POST / HTTP/1.1
+0 < P. 26:36(10) ack 101 win 257

// 抓包看服務器返回

+0 `sleep 1000000`

因爲個人的Wireshark設置的是距離上次抓包間隔時長,所以可以看到其中就復現了延遲確認的40ms
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章