[整理] Socket通信的異常以及處理


一、TCP 握手的異常情況

1. 客戶端第一個「SYN」包丟了。

如果客戶端第一個「SYN」包丟了,也就是服務端根本就不知道客戶端曾經發過包,那么處理流程主要在客戶端。
而在 TCP 協議中,某端的一組「請求-應答」中,在一定時間范圍內,只要沒有收到應答的「ACK」包,無論是請求包對方沒有收到,還是對方的應答包自己沒有收到,均認為是丟包了,都會觸發超時重傳機制。
所以此時會進入重傳「SYN」包。根據《TCP/IP詳解卷Ⅰ:協議》中的描述,此時會嘗試三次,間隔時間分別是 5.8s、24s、48s,三次時間大約是 76s 左右,而大多數伯克利系統將建立一個新連接的最長時間,限制為 75s。
也就是說三次握手第一個「SYN」包丟了,會重傳,總的嘗試時間是 75s。

2. 服務端收到「SYN」並回復的「SYN,ACK」包丟了。

此時服務端已經收到了數據包並回復,如果這個回復的「SYN,ACK」包丟了,站在客戶端的角度,會認為是最開始的那個「SYN」丟了,那么就繼續重傳,就是我們前面說的「錯誤 1」 的流程。
而對服務端而言,如果發送的「SYN,ACK」包丟了,在超時時間內沒有收到客戶端發來的「ACK」包,也會觸發重傳,此時服務端處於 SYN_RCVD 狀態,會依次等待 3s、6s、12s 后,重新發送「SYN,ACK」包。
而這個「SYN,ACK」包的重傳次數,不同的操作系統下有不同的配置,例如在 Linux 下可以通過 tcp_synack_retries 進行配置,默認值為 5。如果這個重試次數內,仍未收到「ACK」應答包,那么服務端會自動關閉這個連接。
同時由於客戶端在沒有收到「SYN,ACK」時,也會進行重傳,當客戶端重傳的「SYN」被收到后,服務端會立即重新發送「SYN,ACK」包。

3. 客戶端最后一次回復「SYN,ACK」的「ACK」包丟了。

如果最后一個「ACK」包丟了,服務端因為收不到「ACK」會走重傳機制,而客戶端此時進入 ESTABLISHED 狀態。
多數情況下,客戶端進入 ESTABLISHED 狀態后,則認為連接已建立,會立即發送數據。但是服務端因為沒有收到最后一個「ACK」包,依然處於 SYN-RCVD 狀態。
那么這里的關鍵,就在於服務端在處於 SYN-RCVD 狀態下,收到客戶端的數據包后如何處理?
這也是比較有爭議的地方,有些資料里會寫到當服務端處於 SYN-RCVD 狀態下,收到客戶端的數據包后,會直接回復 RTS 包響應,表示服務端錯誤,並進入 CLOSE 狀態。
但是這樣的設定有些過於嚴格,試想一下,服務端還在通過三次握手階段確定對方是否真實存在,此時對方的數據已經發來了,那肯定是存在的。所以當服務端處於 SYN-RCVD 狀態下時,接收到客戶端真實發送來的數據包時,會認為連接已建立,並進入 ESTABLISHED 狀態。
那么實際情況為什么會這樣呢?
當客戶端在 ESTABLISHED 狀態下,開始發送數據包時,會攜帶上一個「ACK」的確認序號,所以哪怕客戶端響應的「ACK」包丟了,服務端在收到這個數據包時,能夠通過包內 ACK 的確認序號,正常進入 ESTABLISHED 狀態。

4. 客戶端故意不發最后一次「SYN」包。

前面一直在說正常的異常邏輯,雙方都還算友善,按規矩做事,出現異常主要也是因為網絡等客觀問題,接下來說一個惡意的情況。
如果客戶端是惡意的,在發送「SYN」包后,並收到「SYN,ACK」后就不回復了,那么服務端此時處於一種半連接的狀態,雖然服務端會通過 tcp_synack_retries 配置重試的次數,不會無限等待下去,但是這也是有一個時間周期的。
如果短時間內存在大量的這種惡意連接,對服務端來說壓力就會很大,這就是所謂的 SYN FLOOD 攻擊。這就屬於安全攻防的范疇了。

二、TCP 四次揮手的異常情況

1. 斷開連接的 FIN 包丟了。

我們前面一直強調過,如果一個包發出去,在一定時間內,只要沒有收到對端的「ACK」回復,均認為這個包丟了,會觸發超時重傳機制。而不會關心到底是自己發的包丟了,還是對方的「ACK」丟了。
所以在這里,如果客戶端率先發的「FIN」包丟了,或者沒有收到對端的「ACK」回復,則會觸發超時重傳,直到觸發重傳的次數,直接關閉連接。
對於服務端而言,如果客戶端發來的「FIN」沒有收到,就沒有任何感知。會在一段時間后,也關閉連接。

2. 服務端第一次回復的 ACK 丟了。

此時因為客戶端沒有收到「ACK」應答,會嘗試重傳之前的「FIN」請求,服務端收到后,又會立即再重傳「ACK」。
而此時服務端已經進入 CLOSED-WAIT 狀態,開始做斷開連接前的准備工作。當准備好之后,會回復「FIN,ACK」,注意這個消息是攜帶了之前「ACK」的響應序號的。
只要這個消息沒丟,客戶端可以憑借「FIN,ACK」包中的響應序號,直接從 FIN-WAIT-1 狀態,進入 TIME-WAIT 狀態,開始長達 2MSL 的等待。

附記: 2MSL( Maximum Segment Life) 指兩倍的最大存活時間;

  • 客戶端沒有收到自己的「ACK」應答,會超時重傳「FIN」;
    服務端收到重傳的「FIN」,也會再發「ACK」應答;
  • 客戶端收到自己的「ACK」應答,也不會再發任何消息,包括ACK;
    無論是1還是2,A都需要等待,要取這兩種情況等待時間的最大值,以應對最壞的情況發生,這個最壞情況是:

去向ACK消息最大存活時間(MSL) + 來向FIN消息的最大存活時間(MSL) = 2MSL

3. 服務端發送的 FIN,ACK 丟了。

服務端在超時后會重傳,此時客戶端有兩種情況,要么處於 FIN-WAIT-2 狀態(之前的 ACK 也丟了),會一直等待;要么處於 TIME-WAIT 狀態,會等待 2MSL 時間。
也就是說,在一小段時間內客戶端還在,客戶端在收到服務端發來的「FIN,ACK」包后,也會回復一個「ACK」應答,並做好自己的狀態切換。

4. 客戶端最后回復的 ACK 丟了。

客戶端在回復「ACK」后,會進入 TIME-WAIT 狀態,開始長達 2MSL 的等待,服務端因為沒有收到「ACK」的回復,會重試一段時間,直到服務端重試超時后主動斷開。
或者等待新的客戶端接入后,收到服務端重試的「FIN」消息后,回復「RST」消息,在收到「RST」消息后,復位服務端的狀態。

5. 客戶端收到 ACK 后,服務端跑路了。

客戶端在收到「ACK」后,進入了 FIN-WAIT-2 狀態,等待服務端發來的「FIN」包,而如果服務端跑路了,這個包永遠都等不到。
在 TCP 協議中,是沒有對這個狀態的處理機制的。但是協議不管,系統來湊,操作系統會接管這個狀態,例如在 Linux 下,就可以通過 tcp_fin_timeout 參數,來對這個狀態設定一個超時時間。
需要注意的是,當超過 tcp_fin_timeout 的限制后,狀態並不是切換到 TIME_WAIT,而是直接進入 CLOSED 狀態。

6. 客戶端收到 ACK 后,客戶端自己跑路了。

客戶端收到「ACK」后直接跑路,服務端后續在發送的「FIN,ACK」就沒有接收端,也就不會得到回復,會不斷的走 TCP 的超時重試的機制,此時服務端處於 LAST-ACK 狀態。

那就要分 2 種情況分析:

  • 在超過一定時間后,服務端主動斷開。
  • 收到「RST」后,主動斷開連接。

「RST」消息是一種重置消息,表示當前錯誤了,應該回到初始的狀態。如果客戶端跑路后有新的客戶端接入,會在此發送「SYN」以期望建立連接,此時這個「SYN」將被忽略,並直接回復「FIN,ACK」消息,新客戶端在收到「FIN」消息后是不會認的,並且會回復一個「RST」消息。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM