??大家好,我是小林。昨晚有位讀者問了我這么個問題:大概意思是,一個已經(jīng)建立的 TCP 連接,客戶端中途宕機了,而服務(wù)端此時也沒有數(shù)據(jù)要發(fā)送,一直處于 establish 狀態(tài),客戶端恢復(fù)后,向服務(wù)端建立連接,此時服務(wù)端會怎么處理?看過我的圖解網(wǎng)絡(luò)的讀者都知道,TCP 連接是由「四元組」唯一確認的。然后這個場景中,客戶端的IP、服務(wù)端IP、目的端口并沒有變化,所以這個問題關(guān)鍵要看客戶端發(fā)送的 SYN 報文中的源端口是否和上一次連接的源端口相同。1. 客戶端的 SYN 報文里的端口號與歷史連接不相同如果客戶端恢復(fù)后發(fā)送的 SYN 報文中的源端口號跟上一次連接的源端口號不一樣,此時服務(wù)端會認為是新的連接要建立,于是就會通過三次握手來建立新的連接。那舊連接里處于 establish 狀態(tài)的服務(wù)端最后會怎么樣呢?
2. 客戶端的 SYN 報文里的端口號與歷史連接相同如果客戶端恢復(fù)后,發(fā)送的 SYN 報文中的源端口號跟上一次連接的源端口號一樣,也就是處于 establish 狀態(tài)的服務(wù)端收到了這個 SYN 報文。大家覺得服務(wù)端此時會做什么處理呢?
丟掉 SYN 報文?
回復(fù) RST 報文?
回復(fù) ACK 報文?
剛開始我看到這個問題的時候,也是沒有思路的,因為之前沒關(guān)注過,然后這個問題不能靠猜,所以我就看了 RFC 規(guī)范和看了 Linux 內(nèi)核源碼,最終知道了答案。我不賣關(guān)子,先直接說答案。處于 establish 狀態(tài)的服務(wù)端如果收到了客戶端的 SYN 報文(注意此時的 SYN 報文其實是亂序的,因為 SYN 報文的初始化序列號其實是一個隨機數(shù)),會回復(fù)一個攜帶了正確序列號和確認號的 ACK 報文,這個 ACK 被稱之為 Challenge ACK。接著,客戶端收到這個 Challenge ACK,發(fā)現(xiàn)序列號并不是自己期望收到的,于是就會回 RST 報文,服務(wù)端收到后,就會釋放掉該連接。
RFC 文檔解釋
rfc793 文檔里的第 34 頁里,有說到這個例子。原文的解釋我也貼出來給大家看看。
When the SYN arrives at line 3, TCP B, being in a synchronized state, and the incoming segment outside the window, responds with an acknowledgment indicating what sequence it next expects to hear (ACK 100).
TCP A sees that this segment does not acknowledge anything it sent and, being unsynchronized, sends a reset (RST) because it has detected a half-open connection.
TCP B aborts at line 5. ?
TCP A willcontinue to try to establish the connection;
我就不瞎翻譯了,意思和我在前面用中文說的解釋差不多。
源碼分析
處于 establish 狀態(tài)的服務(wù)端如果收到了客戶端的 SYN 報文時,內(nèi)核會調(diào)用這些函數(shù):tcp_v4_rcv ??->?tcp_v4_do_rcv ????->?tcp_rcv_established ??????->?tcp_validate_incoming ????????->?tcp_send_ack 我們只關(guān)注 tcp_validate_incoming 函數(shù)是怎么處理 SYN 報文的,精簡后的代碼如下:從上面的代碼實現(xiàn)可以看到,處于 establish 狀態(tài)的服務(wù)端,在收到報文后,首先會判斷序列號是否在窗口內(nèi),如果不在,則看看 RST 標(biāo)記有沒有被設(shè)置,如果有就會丟掉。然后如果沒有 RST 標(biāo)志,就會判斷是否有 SYN 標(biāo)記,如果有 SYN 標(biāo)記就會跳轉(zhuǎn)到 syn_challenge 標(biāo)簽,然后執(zhí)行 tcp_send_challenge_ack 函數(shù)。tcp_send_challenge_ack 函數(shù)里就會調(diào)用 tcp_send_ack 函數(shù)來回復(fù)一個攜帶了正確序列號和確認號的 ACK 報文。