TCP三次握手、四次揮手出現(xiàn)意外情況時(shí),如何保證穩(wěn)定可靠?
本文是之前分多篇發(fā)布的文章,現(xiàn)在整理成一篇發(fā)布,調(diào)整了一些小細(xì)節(jié)。
一、序
當(dāng)我們聊到TCP 協(xié)議的時(shí)候,聊的最多的就是三次握手與四次揮手。但是大部分資料和文章,寫的都是正常的情況下的流程。但是你有沒(méi)有想過(guò),三次握手或者四次揮手時(shí),如果發(fā)生異常了,是如何處理的?又是由誰(shuí)來(lái)處理?
TCP 作為一個(gè)靠譜的協(xié)議,在傳輸數(shù)據(jù)的前后,需要在雙端之間建立連接,并在雙端各自維護(hù)連接的狀態(tài)。TCP 并沒(méi)有什么特別之處,在面對(duì)多變的網(wǎng)絡(luò)情況,也只能通過(guò)不斷的重傳和各種算法來(lái)保證可靠性。
建立連接前,TCP 會(huì)通過(guò)三次握手來(lái)保證雙端狀態(tài)正確,然后就可以正常傳輸數(shù)據(jù)了。當(dāng)數(shù)據(jù)傳輸完成,需要斷開(kāi)連接的時(shí)候,TCP 會(huì)通過(guò)四次握手來(lái)完成雙端的斷連,并回收各自的資源。
我們?cè)趯W(xué)習(xí) TCP 建連和斷連時(shí),多數(shù)都在說(shuō)一個(gè)標(biāo)準(zhǔn)的流程,但是網(wǎng)絡(luò)環(huán)境是多變的,很多時(shí)候并不像教科書(shū)那樣標(biāo)準(zhǔn),那么今天就來(lái)聊聊,TCP 三次握手和四次揮手時(shí),如果出現(xiàn)異常情況,是如何處理的?又是由誰(shuí)來(lái)處理?
二、TCP 三次握手
1. 簡(jiǎn)單理解三次握手
雖然是說(shuō)三次握手的異常情況,我們還是先來(lái)了解一下三次握手。
在通過(guò) TCP 傳輸數(shù)據(jù)時(shí),第一步就是要先建立一個(gè)連接。TCP 建立連接的過(guò)程,就是我們常說(shuō)的三次握手。
我們經(jīng)常將三次握手,描述成「請(qǐng)求 → 應(yīng)答 → 應(yīng)答之應(yīng)答」。
至于 TCP 握手為什么是三次?其實(shí)就是要讓雙端都經(jīng)歷一次「請(qǐng)求 → 應(yīng)答」的過(guò)程,來(lái)確認(rèn)對(duì)方還在。網(wǎng)絡(luò)情況是多變的,雙端都需要一次自己主動(dòng)發(fā)起的請(qǐng)求和對(duì)方回復(fù)的應(yīng)答過(guò)程,來(lái)確保對(duì)方和網(wǎng)絡(luò)是正常的。
下面這張圖,是比較經(jīng)典的 TCP 三次握手的消息和雙端狀態(tài)的變化。
我們先來(lái)解釋一下這張圖:
- 在初始時(shí),雙端處于 CLOSE 狀態(tài),服務(wù)端為了提供服務(wù),會(huì)主動(dòng)監(jiān)聽(tīng)某個(gè)端口,進(jìn)入 LISTEN 狀態(tài)。
- 客戶端主動(dòng)發(fā)送連接的「SYN」包,之后進(jìn)入 SYN-SENT 狀態(tài),服務(wù)端在收到客戶端發(fā)來(lái)的「SYN」包后,回復(fù)「SYN,ACK」包,之后進(jìn)入 SYN-RCVD 狀態(tài)。
- 客戶端收到服務(wù)端發(fā)來(lái)的「SYN,ACK」包后,可以確認(rèn)對(duì)方存在,此時(shí)回復(fù)「ACK」包,并進(jìn)入 ESTABLISHED 狀態(tài)。
- 服務(wù)端收到最后一個(gè)「ACK」包后,也進(jìn)入 ESTABLISHED 狀態(tài)。
這是正常的 TCP 三次握手,握手完成后雙端都進(jìn)入 ESTABLISHED 狀態(tài),在此之后,就是正常的數(shù)據(jù)傳輸過(guò)程。
2. TCP 握手的異常情況
三次握手的正常發(fā)包和應(yīng)答,以及雙端的狀態(tài)扭轉(zhuǎn)我們已經(jīng)講了,接下來(lái)就來(lái)看看在這三次握手的過(guò)程中,出現(xiàn)的異常情況。
(1) 客戶端第一個(gè)「SYN」包丟了。
如果客戶端第一個(gè)「SYN」包丟了,也就是服務(wù)端根本就不知道客戶端曾經(jīng)發(fā)過(guò)包,那么處理流程主要在客戶端。
而在 TCP 協(xié)議中,某端的一組「請(qǐng)求-應(yīng)答」中,在一定時(shí)間范圍內(nèi),只要沒(méi)有收到應(yīng)答的「ACK」包,無(wú)論是請(qǐng)求包對(duì)方?jīng)]有收到,還是對(duì)方的應(yīng)答包自己沒(méi)有收到,均認(rèn)為是丟包了,都會(huì)觸發(fā)超時(shí)重傳機(jī)制。
所以此時(shí)會(huì)進(jìn)入重傳「SYN」包。根據(jù)《TCP/IP詳解卷Ⅰ:協(xié)議》中的描述,此時(shí)會(huì)嘗試三次,間隔時(shí)間分別是 5.8s、24s、48s,三次時(shí)間大約是 76s 左右,而大多數(shù)伯克利系統(tǒng)將建立一個(gè)新連接的最長(zhǎng)時(shí)間,限制為 75s。
也就是說(shuō)三次握手第一個(gè)「SYN」包丟了,會(huì)重傳,總的嘗試時(shí)間是 75s。
(2) 服務(wù)端收到「SYN」并回復(fù)的「SYN,ACK」包丟了。
此時(shí)服務(wù)端已經(jīng)收到了數(shù)據(jù)包并回復(fù),如果這個(gè)回復(fù)的「SYN,ACK」包丟了,站在客戶端的角度,會(huì)認(rèn)為是最開(kāi)始的那個(gè)「SYN」丟了,那么就繼續(xù)重傳,就是我們前面說(shuō)的「錯(cuò)誤 1」 的流程。
而對(duì)服務(wù)端而言,如果發(fā)送的「SYN,ACK」包丟了,在超時(shí)時(shí)間內(nèi)沒(méi)有收到客戶端發(fā)來(lái)的「ACK」包,也會(huì)觸發(fā)重傳,此時(shí)服務(wù)端處于 SYN_RCVD 狀態(tài),會(huì)依次等待 3s、6s、12s 后,重新發(fā)送「SYN,ACK」包。
而這個(gè)「SYN,ACK」包的重傳次數(shù),不同的操作系統(tǒng)下有不同的配置,例如在 Linux 下可以通過(guò) tcp_synack_retries 進(jìn)行配置,默認(rèn)值為 5。如果這個(gè)重試次數(shù)內(nèi),仍未收到「ACK」應(yīng)答包,那么服務(wù)端會(huì)自動(dòng)關(guān)閉這個(gè)連接。
同時(shí)由于客戶端在沒(méi)有收到「SYN,ACK」時(shí),也會(huì)進(jìn)行重傳,當(dāng)客戶端重傳的「SYN」被收到后,服務(wù)端會(huì)立即重新發(fā)送「SYN,ACK」包。
(3) 客戶端最后一次回復(fù)「SYN,ACK」的「ACK」包丟了。
如果最后一個(gè)「ACK」包丟了,服務(wù)端因?yàn)槭詹坏健窤CK」會(huì)走重傳機(jī)制,而客戶端此時(shí)進(jìn)入 ESTABLISHED 狀態(tài)。
多數(shù)情況下,客戶端進(jìn)入 ESTABLISHED 狀態(tài)后,則認(rèn)為連接已建立,會(huì)立即發(fā)送數(shù)據(jù)。但是服務(wù)端因?yàn)闆](méi)有收到最后一個(gè)「ACK」包,依然處于 SYN-RCVD 狀態(tài)。
那么這里的關(guān)鍵,就在于服務(wù)端在處于 SYN-RCVD 狀態(tài)下,收到客戶端的數(shù)據(jù)包后如何處理?
這也是比較有爭(zhēng)議的地方,有些資料里會(huì)寫到當(dāng)服務(wù)端處于 SYN-RCVD 狀態(tài)下,收到客戶端的數(shù)據(jù)包后,會(huì)直接回復(fù) RTS 包響應(yīng),表示服務(wù)端錯(cuò)誤,并進(jìn)入 CLOSE 狀態(tài)。
但是這樣的設(shè)定有些過(guò)于嚴(yán)格,試想一下,服務(wù)端還在通過(guò)三次握手階段確定對(duì)方是否真實(shí)存在,此時(shí)對(duì)方的數(shù)據(jù)已經(jīng)發(fā)來(lái)了,那肯定是存在的。
所以當(dāng)服務(wù)端處于 SYN-RCVD 狀態(tài)下時(shí),接收到客戶端真實(shí)發(fā)送來(lái)的數(shù)據(jù)包時(shí),會(huì)認(rèn)為連接已建立,并進(jìn)入 ESTABLISHED 狀態(tài)。
那么實(shí)際情況,為什么會(huì)這樣呢?
當(dāng)客戶端在 ESTABLISHED 狀態(tài)下,開(kāi)始發(fā)送數(shù)據(jù)包時(shí),會(huì)攜帶上一個(gè)「ACK」的確認(rèn)序號(hào),所以哪怕客戶端響應(yīng)的「ACK」包丟了,服務(wù)端在收到這個(gè)數(shù)據(jù)包時(shí),能夠通過(guò)包內(nèi) ACK 的確認(rèn)序號(hào),正常進(jìn)入 ESTABLISHED 狀態(tài)。
(4) 客戶端故意不發(fā)最后一次「SYN」包。
前面一直在說(shuō)正常的異常邏輯,雙方都還算友善,按規(guī)矩做事,出現(xiàn)異常主要也是因?yàn)榫W(wǎng)絡(luò)等客觀問(wèn)題,接下來(lái)說(shuō)一個(gè)惡意的情況。
如果客戶端是惡意的,在發(fā)送「SYN」包后,并收到「SYN,ACK」后就不回復(fù)了,那么服務(wù)端此時(shí)處于一種半連接的狀態(tài),雖然服務(wù)端會(huì)通過(guò) tcp_synack_retries 配置重試的次數(shù),不會(huì)無(wú)限等待下去,但是這也是有一個(gè)時(shí)間周期的。
如果短時(shí)間內(nèi)存在大量的這種惡意連接,對(duì)服務(wù)端來(lái)說(shuō)壓力就會(huì)很大,這就是所謂的 SYN FLOOD 攻擊。
這就屬于安全攻防的范疇了,今天就不討論了,有興趣可以自行了解。
三、TCP 四次揮手
1. 簡(jiǎn)單理解四次揮手
說(shuō)完 TCP 三次握手,繼續(xù)來(lái)分析 TCP 四次揮手的異常情況。
保持行文風(fēng)格,在此之前,我們還是先來(lái)簡(jiǎn)單了解一下 TCP 的四次揮手。
當(dāng)數(shù)據(jù)傳輸完成,需要斷開(kāi)連接的時(shí)候,TCP 會(huì)采取四次揮手的方式,來(lái)安全的斷開(kāi)連接。
為什么握手需要三次,而揮手需要四次呢?
本質(zhì)上來(lái)說(shuō),雙端都需要經(jīng)過(guò)一次「分手」的過(guò)程,來(lái)保證自己和對(duì)端的狀態(tài)正確。本著友好協(xié)商的態(tài)度,你先提出的分手,也要把最大的善意給對(duì)方,不能打了對(duì)方一個(gè)措手不及。你說(shuō)不玩了就不玩了,那以后誰(shuí)還敢和你玩。
下面這張圖,是比較經(jīng)典的 TCP 四次揮手的消息和雙端狀態(tài)的變化。
我們解釋一下這張圖:
- 初始時(shí)雙端還都處于 ESTABLISHED 狀態(tài)并傳輸數(shù)據(jù),某端可以主動(dòng)發(fā)起「FIN」包準(zhǔn)備斷開(kāi)連接,在這里的場(chǎng)景下,是客戶端發(fā)起「FIN」請(qǐng)求。在發(fā)出「FIN」后,客戶端進(jìn)入 FIN-WAIT-1 狀態(tài)。
- 服務(wù)端收到「FIN」消息后,回復(fù)「ACK」表示知道了,并從 ESTABLISHED 狀態(tài)進(jìn)入 CLOSED-WAIT 狀態(tài),開(kāi)始做一些斷開(kāi)連接前的準(zhǔn)備工作。
- 客戶端收到之前「FIN」的回復(fù)「ACK」消息后,進(jìn)入 FIN-WAIT-2 狀態(tài)。而當(dāng)服務(wù)端做好斷開(kāi)前的準(zhǔn)備工作后,也會(huì)發(fā)送一個(gè)「FIN,ACK」的消息給客戶端,表示我也好了,請(qǐng)求斷開(kāi)連接,并在發(fā)送消息后,服務(wù)端進(jìn)入 LAST-ACK 狀態(tài)。
- 客戶端在收到「FIN,ACK」消息后,會(huì)立即回復(fù)「ACK」,表示知道了,并進(jìn)入 TIME_WAIT 狀態(tài),為了穩(wěn)定和安全考慮,客戶端會(huì)在 TIME-WAIT 狀態(tài)等待 2MSL 的時(shí)長(zhǎng),最終進(jìn)入 CLOSED 狀態(tài)。
- 服務(wù)端收到客戶端回復(fù)的「ACK」消息后,直接從 LAST-ACK 狀態(tài)進(jìn)入 CLOSED 狀態(tài)。
正常的經(jīng)過(guò)四次揮手之后,雙端都進(jìn)入 CLOSED 狀態(tài),在此之后,雙端正式斷開(kāi)了連接。
2. TCP 揮手的異常情況
四次揮手的正常發(fā)包和應(yīng)答過(guò)程,我們已經(jīng)簡(jiǎn)單了解了,接下來(lái)就繼續(xù)看看,四次揮手過(guò)程中,出現(xiàn)的異常情況。
(1) 斷開(kāi)連接的 FIN 包丟了。
我們前面一直強(qiáng)調(diào)過(guò),如果一個(gè)包發(fā)出去,在一定時(shí)間內(nèi),只要沒(méi)有收到對(duì)端的「ACK」回復(fù),均認(rèn)為這個(gè)包丟了,會(huì)觸發(fā)超時(shí)重傳機(jī)制。而不會(huì)關(guān)心到底是自己發(fā)的包丟了,還是對(duì)方的「ACK」丟了。
所以在這里,如果客戶端率先發(fā)的「FIN」包丟了,或者沒(méi)有收到對(duì)端的「ACK」回復(fù),則會(huì)觸發(fā)超時(shí)重傳,直到觸發(fā)重傳的次數(shù),直接關(guān)閉連接。
對(duì)于服務(wù)端而言,如果客戶端發(fā)來(lái)的「FIN」沒(méi)有收到,就沒(méi)有任何感知。會(huì)在一段時(shí)間后,也關(guān)閉連接。
(2) 服務(wù)端第一次回復(fù)的 ACK 丟了。
此時(shí)因?yàn)榭蛻舳藳](méi)有收到「ACK」應(yīng)答,會(huì)嘗試重傳之前的「FIN」請(qǐng)求,服務(wù)端收到后,又會(huì)立即再重傳「ACK」。
而此時(shí)服務(wù)端已經(jīng)進(jìn)入 CLOSED-WAIT 狀態(tài),開(kāi)始做斷開(kāi)連接前的準(zhǔn)備工作。當(dāng)準(zhǔn)備好之后,會(huì)回復(fù)「FIN,ACK」,注意這個(gè)消息是攜帶了之前「ACK」的響應(yīng)序號(hào)的。
只要這個(gè)消息沒(méi)丟,客戶端可以憑借「FIN,ACK」包中的響應(yīng)序號(hào),直接從 FIN-WAIT-1 狀態(tài),進(jìn)入 TIME-WAIT 狀態(tài),開(kāi)始長(zhǎng)達(dá) 2MSL 的等待。
(3) 服務(wù)端發(fā)送的 FIN,ACK 丟了。
服務(wù)端在超時(shí)后會(huì)重傳,此時(shí)客戶端有兩種情況,要么處于 FIN-WAIT-2 狀態(tài)(之前的 ACK 也丟了),會(huì)一直等待;要么處于 TIME-WAIT 狀態(tài),會(huì)等待 2MSL 時(shí)間。
也就是說(shuō),在一小段時(shí)間內(nèi)客戶端還在,客戶端在收到服務(wù)端發(fā)來(lái)的「FIN,ACK」包后,也會(huì)回復(fù)一個(gè)「ACK」應(yīng)答,并做好自己的狀態(tài)切換。
(4) 客戶端最后回復(fù)的 ACK 丟了。
客戶端在回復(fù)「ACK」后,會(huì)進(jìn)入 TIME-WAIT 狀態(tài),開(kāi)始長(zhǎng)達(dá) 2MSL 的等待,服務(wù)端因?yàn)闆](méi)有收到「ACK」的回復(fù),會(huì)重試一段時(shí)間,直到服務(wù)端重試超時(shí)后主動(dòng)斷開(kāi)。
或者等待新的客戶端接入后,收到服務(wù)端重試的「FIN」消息后,回復(fù)「RST」消息,在收到「RST」消息后,復(fù)位服務(wù)端的狀態(tài)。
(5) 客戶端收到 ACK 后,服務(wù)端跑路了。
客戶端在收到「ACK」后,進(jìn)入了 FIN-WAIT-2 狀態(tài),等待服務(wù)端發(fā)來(lái)的「FIN」包,而如果服務(wù)端跑路了,這個(gè)包永遠(yuǎn)都等不到。
在 TCP 協(xié)議中,是沒(méi)有對(duì)這個(gè)狀態(tài)的處理機(jī)制的。但是協(xié)議不管,系統(tǒng)來(lái)湊,操作系統(tǒng)會(huì)接管這個(gè)狀態(tài),例如在 Linux 下,就可以通過(guò) tcp_fin_timeout 參數(shù),來(lái)對(duì)這個(gè)狀態(tài)設(shè)定一個(gè)超時(shí)時(shí)間。
需要注意的是,當(dāng)超過(guò) tcp_fin_timeout 的限制后,狀態(tài)并不是切換到 TIME_WAIT,而是直接進(jìn)入 CLOSED 狀態(tài)。
(6) 客戶端收到 ACK 后,客戶端自己跑路了。
客戶端收到「ACK」后直接跑路,服務(wù)端后續(xù)在發(fā)送的「FIN,ACK」就沒(méi)有接收端,也就不會(huì)得到回復(fù),會(huì)不斷的走 TCP 的超時(shí)重試的機(jī)制,此時(shí)服務(wù)端處于 LAST-ACK 狀態(tài)。
那就要分 2 種情況分析:
- 在超過(guò)一定時(shí)間后,服務(wù)端主動(dòng)斷開(kāi)。
- 收到「RST」后,主動(dòng)斷開(kāi)連接。
「RST」消息是一種重置消息,表示當(dāng)前錯(cuò)誤了,應(yīng)該回到初始的狀態(tài)。如果客戶端跑路后有新的客戶端接入,會(huì)在此發(fā)送「SYN」以期望建立連接,此時(shí)這個(gè)「SYN」將被忽略,并直接回復(fù)「FIN,ACK」消息,新客戶端在收到「FIN」消息后是不會(huì)認(rèn)的,并且會(huì)回復(fù)一個(gè)「RST」消息。
四、小結(jié)時(shí)刻
本文聊了 TCP 在三次握手和四次揮手的時(shí)候,出現(xiàn)異常的處理邏輯。
大多數(shù)情況下,都是依賴超時(shí)重傳來(lái)保證 TCP 的可靠性,但是重傳的次數(shù),狀態(tài)的轉(zhuǎn)換,以及有哪些狀態(tài)是被系統(tǒng)接管,這些細(xì)節(jié),就是本文的主題。