1. 程式人生 > 實用技巧 >TCP實戰一(三握四揮、流量控制)

TCP實戰一(三握四揮、流量控制)

上一篇博文已經介紹了tcpdump的一些基本操作與命令,今天這篇博文將帶你解密如何利用wireshark對tcpdump抓到的資料包進行視覺化分析!

參考文獻:https://zhuanlan.zhihu.com/p/142665708

目錄

1.Wireshark視覺化分析ping過程(分析ICMP協議)

2.Wireshark視覺化分析TCP三次握手以及四次揮手過程

3.TCP三次握手異常情況實戰分析

4.Wireshark視覺化分析TCP流量控制

本篇博文的精華:

Q1:TCP 第一次握手 SYN 丟包,客戶端的操作是什麼?

通過實驗一的實驗結果,我們可以得知,當客戶端發起的 TCP 第一次握手 SYN 包,如果在初始RTO內沒收到服務端的 ACK,就會重傳 SYN 資料包,通常情況下初始RTO的值都為1,隨後,每次的RTO值都是前一次的2倍,直到 SYN 包的重傳次數到達tcp_syn_retries

值後,客戶端不再發送 SYN 包。(但是通常情況下你也不能把tcp_syn_retries的值設定的過大,博主嘗試過當此值設定為8以上時,效果都和8一樣)

Q2:TCP 第二次握手 SYN、ACK 丟包,服務端與客戶端分別進行什麼操作?

通過實驗二的實驗結果,我們可以得知,當第二次握手的 SYN、ACK 丟包時,客戶端會超時重發 SYN 包,服務端也會發送兩次SYN+ACK 包,第一個 SYN+ACK 資料包是響應客戶端超時重傳的 SYN 資料包,第二個SYN+ACK 資料包則是超時重傳的。

客戶端 SYN 包超時重傳的最大次數,是由 tcp_syn_retries 決定的,預設值是 5 次;服務端 SYN、ACK 包時重傳的最大次數,是由 tcp_synack_retries 決定的,預設值是 5 次。且每次的 RTO 都是上一次的2倍。

參考:RTO的理解以及計算方法

Q3:TCP 第三次握手 ACK 丟包,客戶端和服務端分別有什麼操作

在建立 TCP 連線時,如果第三次握手的 ACK,服務端無法收到,則服務端就會短暫處於SYN_RECV狀態,而客戶端會處於ESTABLISHED狀態。

由於服務端一直收不到 TCP 第三次握手的 ACK,則會一直重傳 SYN、ACK 包,直到重傳次數超過tcp_synack_retries值(預設值 5 次)後,服務端就會斷開 TCP 連線。

而客戶端則會有兩種情況:

  • 如果客戶端沒傳送資料包,一直處於ESTABLISHED狀態,然後經過 2 小時 11 分 15 秒才可以發現一個「死亡」連線,於是客戶端連線就會斷開連線。
  • 如果客戶端傳送了資料包,一直沒有收到服務端對該資料包的確認報文,則會一直重傳該資料包,直到重傳次數超過tcp_retries2值(預設值 15 次)後,客戶端就會斷開 TCP 連線。

1.Wireshark視覺化分析ping過程(分析ICMP協議)

Wireshark 除了可以抓包外,還提供了視覺化分析網路包的圖形頁面,同時,還內建了一系列的彙總分析工具。

首先,我們以 ping 例子來說,我們可以使用下面的命令,把抓取的資料包儲存到 tcpdata.pcap 檔案

tcpdump -nn -v -c  icmp -w /home/xx/桌面/tcpdata.pcap

接著把 ping.pcap 檔案拖到電腦,再用 Wireshark 開啟它。開啟後,你就可以看到下面這個介面:

是吧?在 Wireshark 的頁面裡,可以更加直觀的分析資料包,不僅展示各個網路包的頭部資訊,還會用不同的顏色來區分不同的協議,由於這次抓包只有 ICMP 協議,所以只有紫色的條目。

接著,在網路包列表中選擇某一個網路包後,在其下面的網路包詳情中,可以更清楚的看到,這個網路包在協議棧各層的詳細資訊。比如,以編號 1 的網路包為例子:

  • 可以在資料鏈路層,看到 MAC 包頭資訊,如源 MAC 地址和目標 MAC 地址等欄位;
  • 可以在 IP 層,看到 IP 包頭資訊,如源 IP 地址和目標 IP 地址、TTL、IP 包長度、協議等 IP 協議各個欄位的數值和含義;
  • 可以在 ICMP 層,看到 ICMP 包頭資訊,比如 Type、Code 等 ICMP 協議各個欄位的數值和含義;

以下這張圖是IP頭部資訊與ICMP頭部資訊,可以與上圖對照進行分析

Wireshark 用了分層的方式,展示了各個層的包頭資訊,把“不可見”的資料包,清清楚楚的展示了給我們,從 ping 的例子中,我們可以看到網路分層就像有序的分工,每一層都有自己的責任範圍和資訊,上層協議完成工作後就交給下一層,最終形成一個完整的網路包。

2.Wireshark視覺化分析TCP三次握手以及四次揮手過程

既然學會了 tcpdump 和 Wireshark 兩大網路分析利器,那我們快馬加鞭,接下用它倆抓取和分析 HTTP 協議網路包,並理解 TCP 三次握手和四次揮手的工作原理。

本次例子,我們將要訪問的依舊是 www.baidu.com。在終端一用 tcpdump 命令抓取資料包,並利用curl命令訪問www.baidu.com網址。

在tcpdump抓取資料包的終端中按下 Ctrl+C 停止 tcpdump,並把得到的 http_info.pcap 取出到電腦。

使用 Wireshark 開啟 http_info.pcap 後,你就可以在 Wireshark 中,看到如下的介面:

我們都知道 HTTP 是基於 TCP 協議進行傳輸的,那麼:

  • 最開始的 3 個包就是 TCP 三次握手建立連線的包
  • 中間是 HTTP 請求和響應的包
  • 而最後的 4 個包則是 TCP 斷開連線的揮手包

Wireshark 可以用時序圖的方式顯示資料包互動的過程,從選單欄中,點選 統計 (Statistics) -> 流量圖 (Flow Graph),然後,在彈出的介面中的「流量型別」選擇 「TCP Flows」,你可以更清晰的看到,整個過程中 TCP 流的執行過程:

為什麼三次握手連線過程的 Seq 是 0 ?

其實是因為Wireshark 工具幫我們做了優化,它預設顯示的是序列號 seq 是相對值,而不是真實值。

如果你想看到實際的序列號的值,可以右鍵選單, 然後找到「協議首選項」,接著找到「Relative Seq」後,把它給取消。

取消後的流量圖如下(也就是真實的seq值):

可見,客戶端和服務端的序列號實際上是不同的,並且序列號初始是一個隨機值。這其實跟我們書上看到的 TCP 三次握手和四次揮手基本上是一致的。

有關tcp三次握手、四次揮手可以檢視我的部落格:備戰研發崗—計算機網路

在此以圖的形式簡單回顧一下整個過程:

為什麼有些情況下抓到的 TCP 揮手是三次,而不是書上說的四次?例如:

因為伺服器端收到客戶端的FIN後,伺服器端同時也要關閉連線,這樣就可以把ACKFIN合併到一起傳送,節省了一個包,變成了“三次揮手”。

而通常情況下,伺服器端收到客戶端的FIN後,很可能還沒傳送完資料,所以就會先回復客戶端一個ACK包,稍等一會兒,完成所有資料包的傳送後,才會傳送FIN包,這也就是四次揮手了。

3.TCP三次握手異常情況實戰分析

TCP三次握手的過程也許大夥都背的滾瓜爛熟了,打你有沒有想過在三次握手的過程中可能會發生一下幾個問題:

  • TCP 第一次握手的 SYN 丟包了,會發生了什麼?
  • TCP 第二次握手的 SYN、ACK 丟包了,會發生什麼?
  • TCP 第三次握手的 ACK 包丟了,會發生什麼?

不就是丟包嘛,重傳資料包不就好了嘛,真的是這麼簡單嗎?那你能回答出以下的問題嗎

  • 那會重傳幾次?
  • 超時重傳的時間 RTO 會如何變化?
  • 在 Linux 下如何設定重傳次數?
  • ....

接下來用三個實驗案例帶你解密整個過程,幫助你全面剖析TCP三次握手。

實驗環境

在VMware上部署兩個虛擬機器,一臺當作客戶機,一臺當作伺服器。兩臺虛擬機器關係如下圖:

  • 客戶端和服務端都基於 CentOs 6.5 Linux,Linux 核心版本 2.6.32
  • 服務端 192.168.127.150,apache web 服務
  • 客戶端 192.168.127.151
  • curl是一個命令列工具,通過指定的URL來上傳或下載資料,並將資料展示出來。curl中的c表示client,而URL,就是URL.
  • Telnet協議是TCP/IP協議家族中的一員,是Internet遠端登陸服務的標準協議和主要方式。它為使用者提供了在本地計算機上完成遠端主機工作的能力。Telnet是常用的遠端控制Web伺服器的方法。

有些人可能不懂如何在服務端部署apache web服務,詳情見連結:CentOS6.5環境下搭建Apache httpd伺服器

3.1 實驗一:TCP 第一次握手 SYN 丟包

為了模擬 TCP 第一次握手 SYN 丟包的情況,我的步驟如下:

  • 客戶端執行tcpdump命令,並利用curl指令成功連上服務端。
  • 切換到服務端,立馬拔掉服務端的網線(可以採用ifdown 網絡卡名的命令進行操作)
  • 最後切換到客戶端,再次執行curl指令

從上圖可以看出centos6.5(核心版本 2.6.32)引數tcp_syn_retries的預設值為5,為了實驗效果我們把該值設定為6。

按照上述的實驗步驟,最終得到的實驗結果如下圖所示:

從上圖可以發現, 客戶端發起了 SYN 包後,一直沒有收到服務端的 ACK ,基於超時重傳機制所以客戶端根據tcp_syn_retries的值(6)重傳了 6 次syn資料包(一共包含7個Flags[S],其中第一個是正常傳送SYN資料包,後面6個為超時重傳發送的syn資料包),並且每次重傳syn資料包的間隔時間是不同的並且是有規律的:1,2,4,8,16,32.centos6.5預設RTO(Retransmission Timeout)的初始值為1,

RTO指的是,傳送資料包在一定的時間週期內沒有收到相應的ACK,等待一定的時間,超時之後就認為這個資料包丟失,就會重新發送。這個等待時間被稱為RTO.  

隨後,每次的RTO值都是前一次的2倍。當超過最大重傳次數後,客戶端不再發送 SYN 包。

第六次傳送syn資料包,需要等待64s才知道該連線也超時了,此時重發次數已達到tcp_syn_retries的值,所以最後在21:56:14(傳送第一個syn資料包的時間) + (1+2+4+8+16+32+64) = 21: 58:21curl命令返回連線失敗。

實驗一的實驗小結

Q:TCP 第一次握手 SYN 丟包,客戶端的操作是什麼?

通過實驗一的實驗結果,我們可以得知,當客戶端發起的 TCP 第一次握手 SYN 包,如果在初始RTO內沒收到服務端的 ACK,就會重傳 SYN 資料包,通常情況下初始RTO的值都為1,隨後,每次的RTO值都是前一次的2倍,直到 SYN 包的重傳次數到達tcp_syn_retries值後,客戶端不再發送 SYN 包。(但是通常情況下你也不能把tcp_syn_retries的值設定的過大,博主嘗試過當此值設定為8以上時,效果都和8一樣)


記錄一下:在沒有翻牆的情況下,博主利用客戶機curl命令訪問外網例如谷歌、youtube等等,按理來說在tcp_syn_retries = 6的情況下,tcpdump指令應該抓到7個syn資料包,但是實驗結果如下所示:

在tcp_syn_retries = 3的情況下,實驗結果如下:

綜上所述,當你在客戶端利用curl命令連結外網,tcp_syn_retries的有效值最大為4。

  • 意思就是如果你設定的tcp_syn_retries的值大於4,tcpdump命令只能抓到客戶端重複傳送4次syn資料包,並且傳送完4次syn資料包後,客戶端還會接收到服務端傳送的RST+ACK包。
  • 如果你設定的tcp_syn_retries的值小於4,則tcpdump命令就只會抓到客戶端重複傳送 tcp_syn_retries的值 次syn資料包,並且在距離第一次傳送syn資料包21秒後,客戶端接收到服務端傳送的RST+ACK。包。

原因可能在於linux核心版本升級了,所以對此進行了優化(這是我的猜想)。

3.2 實驗二:TCP 第二次握手 SYN、ACK 丟包

為了模擬客戶端收不到服務端第二次握手 SYN、ACK 包,我的做法是在客戶端加上防火牆限制,直接粗暴的把來自服務端的資料都丟棄,防火牆的配置如下:

接著在客戶端執行tcpdump命令進行資料包的抓取

最後在客戶端執行 curl 指令:

由上圖date命令間隔可知:執行curl命令前後花了 63s curl報錯退出了。有些人可能立馬就會聯想到63s不正是實驗一客戶端5次重發syn資料包的總時間嗎(1+2+4+8+16)+32 = 63s.實驗二不是探討TCP 第二次握手 SYN、ACK 丟包嗎,這與實驗一有關係嗎?不急,接下來就帶你解密。

我們把客戶端抓取的資料包利用Wireshark進行分析:

  • 第1行:客戶端成功向服務端傳送syn資料包
  • 第2行:Service成功接收到來自Client端傳送syn資料包,所以向Client端傳送 syn+ack 資料包
  • 由於我們在客戶端加上防火牆限制,直接粗暴的把來自服務端的資料都丟棄,所以Client端是收不到來自Service端的ACK+SYN資料包。
  • 第3行:Client端又重新向Service端傳送一個seq相同的syn資料包。
  • 因為距離第一次傳送syn資料包已經過去了1s,這1s正好滿足RTO的初始值,此時Client端認為第一次傳送syn資料包超時了,所以進行第一次超時重傳。
  • 第4行:在第2行Service端向Client端傳送ACK+SYN資料包後,沒有收到來自Client端的ACK資料包,所以選擇了超時重傳ACK+SYN資料包。
  • 第5行:Client端超時重傳的 SYN 包又抵達了服務端,服務端收到後,重傳定時器就重新計時,然後向Client端回了 SYN、ACK 包。
  • 所以出現了2次Service端向Client端傳送ACK+SYN資料包。
  • 第6-17行重複上述過程。
  • 最後,Client端重發syn資料包達到了5次 (tcp_syn_retries 預設值 5 次),就不會再發送syn資料包了。

根據以上兩張圖片,可以發現

  • 客戶端發起 SYN 後,由於防火牆遮蔽了服務端的所有資料包,所以 curl 是無法收到服務端的 SYN、ACK 包,當發生超時後,就會重傳 SYN 包
  • 服務端收到客戶的 SYN 包後,就會回 SYN、ACK 包,但是客戶端一直沒有回 ACK,服務端在超時後,重傳了 SYN、ACK 包,接著一會,客戶端超時重傳的 SYN 包又抵達了服務端,服務端收到後,超時定時器就重新計時,然後回了 SYN、ACK 包,所以相當於服務端的超時定時器只觸發了一次,又被重置了。
  • 最後,客戶端 SYN 超時重傳次數達到了 5 次(tcp_syn_retries 預設值 5 次),就不再繼續傳送 SYN 包了。

所以,我們可以發現,當第二次握手的 SYN、ACK 丟包時,客戶端會超時重發 SYN 包,服務端也會發送兩次SYN+ACK 包,第一個 SYN+ACK 資料包是響應客戶端超時重傳的 SYN 資料包,第二個SYN+ACK 資料包則是超時重傳的。

Q:客戶端設定了防火牆,遮蔽了服務端的網路包,為什麼 tcpdump 還能抓到服務端的網路包?

新增 iptables 限制後, tcpdump 是否能抓到包 ,這要看新增的 iptables 限制條件:

  • 如果新增的是INPUT規則,則可以抓得到包
  • 如果新增的是OUTPUT規則,則抓不到包

網路包進入主機後的順序如下:

  • 進來的順序 Wire(網線) -> NIC(網絡卡) ->tcpdump -> netfilter/iptables(防火牆)
  • 出去的順序iptables -> tcpdump-> NIC -> Wire

Q:tcp_syn_retries 是限制 SYN 重傳次數,那第二次握手 SYN、ACK 限制最大重傳次數是多少?

如圖所示,TCP 第二次握手 SYN、ACK 包的最大重傳次數預設值是5次。

為了驗證 SYN、ACK 包最大重傳次數是 5 次,我們繼續做下實驗,我們先把客戶端的tcp_syn_retries設定為 1,表示客戶端 SYN 最大超時次數是 1 次,目的是為了防止多次重傳 SYN,把服務端 SYN、ACK 超時定時器重置。

接著,還是如上面的步驟:

  • 客戶端配置防火牆遮蔽服務端的資料包
  • 客戶端 tcpdump 抓取 curl 執行時的資料包

把抓取的資料包,用 Wireshark 開啟分析,顯示的時序圖如下:

從上圖,我們可以分析出:

  • 客戶端的 SYN 只超時重傳了 1 次,因為tcp_syn_retries值為 1
  • 服務端應答了客戶端超時重傳的 SYN 包後,由於一直收不到客戶端的 ACK 包,所以服務端一直在超時重傳 SYN、ACK 包,每次的 RTO 也是指數上漲的,一共超時重傳了 5 次,因為tcp_synack_retries值為 5

接著,我把tcp_synack_retries 設定為 2,tcp_syn_retries依然設定為 1:

$ echo 2 > /proc/sys/net/ipv4/tcp_synack_retries
$ echo 1 > /proc/sys/net/ipv4/tcp_syn_retries

依然保持一樣的實驗步驟進行操作,接著把抓取的資料包,用 Wireshark 開啟分析,顯示的時序圖如下:

可見:

  • 客戶端的 SYN 包只超時重傳了 1 次,符合 tcp_syn_retries 設定的值;
  • 服務端的 SYN、ACK 超時重傳了 2 次,符合 tcp_synack_retries 設定的值

實驗二的實驗小結

Q:TCP 第二次握手 SYN、ACK 丟包,服務端與客戶端分別進行什麼操作?

通過實驗二的實驗結果,我們可以得知,當第二次握手的 SYN、ACK 丟包時,客戶端會超時重發 SYN 包,服務端也會發送兩次SYN+ACK 包,第一個 SYN+ACK 資料包是響應客戶端超時重傳的 SYN 資料包,第二個SYN+ACK 資料包則是超時重傳的。

客戶端 SYN 包超時重傳的最大次數,是由 tcp_syn_retries 決定的,預設值是 5 次;服務端 SYN、ACK 包時重傳的最大次數,是由 tcp_synack_retries 決定的,預設值是 5 次。且每次的 RTO 都是上一次的2倍。

參考:RTO的理解以及計算方法

3.3 實驗三:TCP 第三次握手 ACK 丟包

為了模擬 TCP 第三次握手 ACK 包丟,我的實驗方法是在服務端配置防火牆,遮蔽客戶端 TCP 報文中標誌位是 ACK 的包,也就是當服務端收到客戶端的 TCP ACK 的報文時就會丟棄,iptables 配置命令如下:

接著,在客戶端執行如下 tcpdump 命令:

然後,客戶端向服務端發起 telnet,因為 telnet 命令是會發起 TCP 連線,所以用此命令做測試:

此時,由於服務端收不到第三次握手的 ACK 包,所以一直處於SYN_RECV狀態:

而客戶端是已完成 TCP 連線建立,處於ESTABLISHED狀態:

過了 一陣子,觀察發現服務端的 TCP 連線不見了:

但是客戶端依然還是處於ESTABLISHED狀態:

接著,在剛才客戶端建立的 telnet 會話,輸入 123456 字元,進行傳送:

持續「好長」一段時間,客戶端的 telnet 才斷開連線:

以上就是本次的實現三的現象,這裡存在兩個疑點:

  • 為什麼服務端原本處於SYN_RECV狀態的連線,過 一陣子後就消失了?
  • 為什麼客戶端 telnet 輸入 123456 字元後,過了好長一段時間,telnet 才斷開連線?

不著急,我們把剛抓的資料包,用 Wireshark 開啟分析,顯示的時序圖如下:

上圖的流程如下:

  • 客戶端傳送 SYN 包給服務端,服務端收到後,回了個 SYN、ACK 包給客戶端,此時服務端的 TCP 連線處於SYN_RECV狀態;
  • 客戶端收到服務端的 SYN、ACK 包後,給服務端回了個 ACK 包,此時客戶端的 TCP 連線處於ESTABLISHED狀態;
  • 由於服務端配置了防火牆,遮蔽了客戶端的 ACK 包,所以服務端一直處於SYN_RECV狀態,沒有進入ESTABLISHED狀態,tcpdump 之所以能抓到客戶端的 ACK 包,是因為資料包進入系統的順序是先進入 tcpudmp,後經過 iptables;
  • 接著,服務端超時重傳了 SYN、ACK 包,重傳了 5 次後,也就是超過 tcp_synack_retries 的值(預設值是 5),然後就沒有繼續重傳了,此時服務端的 TCP 連線主動中止了,所以剛才處於 SYN_RECV 狀態的 TCP 連線斷開了,而客戶端依然處於ESTABLISHED狀態;
  • 雖然服務端 TCP 斷開了,但過了一段時間,發現客戶端依然處於ESTABLISHED狀態,於是就在客戶端的 telnet 會話輸入了 123456 字元;
  • 此時由於服務端已經斷開連線,客戶端傳送的資料報文,一直在超時重傳,每一次重傳,RTO 的值是指數增長的,所以持續了好長一段時間,客戶端的 telnet 才報錯退出了,此時共重傳了 15 次。

通過這一波分析,剛才的兩個疑點已經解除了:

  • 服務端在重傳 SYN、ACK 包時,超過了最大重傳次數tcp_synack_retries,於是服務端的 TCP 連線主動斷開了。
  • 客戶端向服務端傳送資料包時,由於服務端的 TCP 連線已經退出了,所以資料包一直在超時重傳,共重傳了 15 次, telnet 就 斷開了連線。

Q:TCP 第一次握手的 SYN 包超時重傳最大次數是由 tcp_syn_retries 指定,TCP 第二次握手的 SYN、ACK 包超時重傳最大次數是由 tcp_synack_retries 指定,那 TCP 建立連線後的資料包最大超時重傳次數是由什麼引數指定呢?

TCP 建立連線後的資料包傳輸,最大超時重傳次數是由tcp_retries2指定,預設值是 15 次,如下:

$ cat /proc/sys/net/ipv4/tcp_retries2
15

如果 15 次重傳都做完了,TCP 就會告訴應用層說:“搞不定了,包怎麼都傳不過去!”

Q:那如果客戶端不傳送資料,什麼時候才會斷開處於 ESTABLISHED 狀態的連線?

這裡就需要利用到TCP的保活機制,保活機制的原理如下:

定義一個時間段,在這個時間段內,如果沒有任何連線相關的活動,TCP保活機制會開始作用,每隔一個時間間隔,傳送一個「探測報文」,該探測報文包含的資料非常少,如果連續幾個探測報文都沒有得到響應,則認為當前的 TCP 連線已經死亡,系統核心將錯誤資訊通知給上層應用程式。

在Centos6.5(linux核心版本2.6)可以有對應的引數可以設定保活時間、保活探測的次數、保活探測的時間間隔,以下都為預設值:

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75
net.ipv4.tcp_keepalive_probes=9
  • tcp_keepalive_time = 7200:表示保活時間是 7200 秒(2小時),也就 2 小時內如果沒有任何連線相關的活動,則會啟動保活機制
  • tcp_keepalive_intvl = 75:表示每次檢測間隔 75 秒;
  • tcp_keepalive_probes = 9:表示檢測 9 次無響應,認為對方是不可達的,從而中斷本次的連線。

也就是說在 Linux 系統中,最少需要經過 2 小時 11 分 15 秒才可以發現一個「死亡」連線。

這個時間是有點長的,所以如果我抓包足夠久,或許能抓到探測報文。

實驗三小結:

Q:TCP 第三次握手 ACK 丟包,客戶端和服務端分別有什麼操作

在建立 TCP 連線時,如果第三次握手的 ACK,服務端無法收到,則服務端就會短暫處於SYN_RECV狀態,而客戶端會處於ESTABLISHED狀態。

由於服務端一直收不到 TCP 第三次握手的 ACK,則會一直重傳 SYN、ACK 包,直到重傳次數超過tcp_synack_retries值(預設值 5 次)後,服務端就會斷開 TCP 連線。

而客戶端則會有兩種情況:

  • 如果客戶端沒傳送資料包,一直處於ESTABLISHED狀態,然後經過 2 小時 11 分 15 秒才可以發現一個「死亡」連線,於是客戶端連線就會斷開連線。
  • 如果客戶端傳送了資料包,一直沒有收到服務端對該資料包的確認報文,則會一直重傳該資料包,直到重傳次數超過tcp_retries2值(預設值 15 次)後,客戶端就會斷開 TCP 連線。

4.Wireshark視覺化分析TCP流量控制

TCP 為了防止傳送方無腦的傳送資料,導致接收方緩衝區被填滿,所以就有了滑動視窗的機制,它可利用接收方的接收視窗來控制傳送方一次性要傳送的資料量,也就是流量控制。

接收視窗是由接收方指定的值,儲存在 TCP 頭部中,它可以告訴傳送方自己的 TCP 緩衝空間區大小,這個緩衝區是給應用程式讀取資料的空間:

  • 如果應用程式讀取了緩衝區的資料,那麼緩衝空間區的就會把被讀取的資料移除
  • 如果應用程式沒有讀取資料,則資料會一直滯留在緩衝區。

假設接收方接收到資料後,應用層能很快的從緩衝區裡讀取資料,那麼視窗大小會一直保持不變,過程如下:

但是現實中伺服器會出現繁忙的情況,當應用程式讀取速度慢,那麼快取空間會慢慢被佔滿,於是為了保證傳送方傳送的資料不會超過緩衝區大小,伺服器則會調整視窗大小的值,接著通過 ACK 報文通知給對方,告知現在的接收視窗大小,從而控制傳送方傳送的資料大小。

零視窗通知與視窗探測

假設接收方處理資料的速度跟不上接收資料的速度,快取就會被佔滿,從而導致接收視窗為 0,當傳送方接收到零視窗通知時,就會停止傳送資料。

如下圖,可以接收方的視窗大小在不斷的收縮至 0:

接著,傳送方會定時傳送視窗大小探測報文,以便及時知道接收方視窗大小的變化。

以下圖 Wireshark 分析圖作為例子說明:

  • 傳送方傳送了資料包 1 給接收方,接收方收到後,由於緩衝區被佔滿,回了個零視窗通知;
  • 傳送方收到零視窗通知後,就不再發送資料了,直到過了3.4秒後,傳送了一個 TCP Keep-Alive 報文,也就是視窗大小探測報文;
  • 當接收方收到視窗探測報文後,就立馬回一個視窗通知,但是視窗大小還是 0;
  • 傳送方發現視窗還是 0,於是繼續等待了6.8(翻倍) 秒後,又傳送了視窗探測報文,接收方依然還是回了視窗為 0 的通知;
  • 傳送方發現視窗還是 0,於是繼續等待了13.5(翻倍) 秒後,又傳送了視窗探測報文,接收方依然還是回了視窗為 0 的通知;

可以發現,這些視窗探測報文以 3.4s、6.5s、13.5s 的間隔出現,說明超時時間會翻倍遞增。

這連線暫停了 25s,想象一下你在打王者的時候,25s 的延遲你還能上王者嗎?

傳送視窗的分析

(1) 在 Wireshark 看到的 Windows size 也就是 " win = ",這個值表示傳送視窗嗎?

這不是傳送視窗,而是在向對方宣告自己的接收視窗。

(2) 如何在包裡看出傳送視窗的大小?

很遺憾,沒有簡單的辦法,傳送視窗雖然是由接收視窗決定,但是它又可以被網路因素影響,也就是擁塞視窗,實際上傳送視窗是值是 min(擁塞視窗,接收視窗)。

(3)傳送視窗和 MSS 有什麼關係?MSS(Maximum Segment Size)最大報文段長度:表示TCP報文段中的資料欄位的最大長度

傳送視窗決定了一口氣能發多少位元組,而 MSS 決定了這些位元組要分多少包才能發完。

舉個例子,如果傳送視窗為 16000 位元組的情況下,如果 MSS 是 1000 位元組,那就需要傳送 1600/1000 = 16 個包。

(4)傳送方在一個視窗發出 n 個包,是不是需要 n 個 ACK 確認報文?

不一定,因為 TCP 有累計確認機制,所以當收到多個數據包時,只需要應答最後一個數據包的 ACK 報文就可以了。