大家好,我是小林。
【資料圖】
有位讀者在面試字節時,被問到這么個問題:
概括起來,是這兩個問題:
TCP 三次握手中,客戶端收到的第二次握手中 ack 確認號不是自己期望的,會發生什么?是直接丟棄 or 回 RST 報文?
什么情況下會收到不正確的 ack(第二次握手中的 ack) 呢?
問題解答不賣關子,直接說這個問題,是回 RST 報文。過程如下圖:
三次握手避免歷史連接
當客戶端連續發送多次建立連接的 SYN 報文,然后在網絡擁堵的情況,就會發生客戶端收到不正確的 ack 的情況。具體過程如下:
客戶端先發送了 SYN(seq = 90) 報文,但是被網絡阻塞了,服務端并沒有收到,接著客戶端又重新發送了 SYN(seq = 100) 報文,注意不是重傳 SYN,重傳的 SYN 的序列號是一樣的?!概f SYN 報文」比「最新的 SYN 」 報文早到達了服務端,那么此時服務端就會回一個 SYN + ACK 報文給客戶端,此報文的確認號是 91(90+1)??蛻舳耸盏胶?,發行自己期望收到的確認號應該是 100+1,而不是 90 + 1,于是就會回 RST 報文。服務端收到 RST 報文后,就會中止連接。后續最新的 SYN 抵達了服務端后,客戶端與服務端就可以正常的完成三次握手了。上述中的「舊 SYN 報文」稱為歷史連接,TCP 使用三次握手建立連接的最主要原因就是防止「歷史連接」初始化了連接。
我們也可以從 RFC 793 知道 TCP 連接使用三次握手的首要原因:
The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.
簡單來說,三次握手的首要原因是為了防止舊的重復連接初始化造成混亂。RFC 給出的三次握手防止歷史連接的案例圖如下:
RFC 793
如果是兩次握手連接,就無法阻止歷史連接,那為什么 TCP 兩次握手為什么無法阻止歷史連接呢?
我先直接說結論,主要是因為在兩次握手的情況下,「被動發起方」沒有中間狀態給「主動發起方」來阻止歷史連接,導致「被動發起方」可能建立一個歷史連接,造成資源浪費。
你想想,兩次握手的情況下,「被動發起方」在收到 SYN 報文后,就進入 ESTABLISHED 狀態,意味著這時可以給對方發送數據給,但是「主動發」起方此時還沒有進入 ESTABLISHED 狀態,假設這次是歷史連接,主動發起方判斷到此次連接為歷史連接,那么就會回 RST 報文來斷開連接,而「被動發起方」在第一次握手的時候就進入 ESTABLISHED 狀態,所以它可以發送數據的,但是它并不知道這個是歷史連接,它只有在收到 RST 報文后,才會斷開連接。
兩次握手無法阻止歷史連接
可以看到,上面這種場景下,「被動發起方」在向「主動發起方」發送數據前,并沒有阻止掉歷史連接,導致「被動發起方」建立了一個歷史連接,又白白發送了數據,妥妥地浪費了「被動發起方」的資源。
因此,要解決這種現象,最好就是在「被動發起方」發送數據前,也就是建立連接之前,要阻止掉歷史連接,這樣就不會造成資源浪費,而要實現這個功能,就需要三次握手。
源碼分析我說回 RST 就回 RST 嗎?當然不是了,肯定得用源碼證明我說的這個結論。
聽到要源碼分析,可能有的同學就慫了。
其實要分析我們今天這個問題,只要懂 if else 就行了,我也會用中文來表述代碼的邏輯,所以單純看我的文字也是可以的。
這次我們重點分析的是,在 SYN_SENT 狀態下,收到不正確的確認號的 syn+ack 報文是如何處理的。
處于 SYN_SENT 狀態下的客戶端,在收到服務端的 syn+ack 報文后,最終會調用 tcp_rcv_state_process,在這里會根據 TCP 狀態做對應的處理,這里我們只關注 SYN_SENT 狀態。
// net/ipv4/tcp_ipv4.cint tcp_rcv_state_process(struct sock *sk, struct sk_buff *skb){ ... int queued = 0; ... switch (sk->sk_state) { case TCP_CLOSE: ... case TCP_LISTEN: ... case TCP_SYN_SENT: .... queued = tcp_rcv_synsent_state_process(sk, skb, th); if (queued >= 0) return queued; ... }
可以看到,接下來,會繼續調用 tcp_rcv_synsent_state_process 函數。
static int tcp_rcv_synsent_state_process(struct sock *sk, struct sk_buff *skb, const struct tcphdr *th){ .... if (th->ack) { /* rfc793: * "If the state is SYN-SENT then * first check the ACK bit * If the ACK bit is set * If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send * a reset (unless the RST bit is set, if so drop * the segment and return)" */ // ack 的確認號不是預期的 if (!after(TCP_SKB_CB(skb)->ack_seq, tp->snd_una) || after(TCP_SKB_CB(skb)->ack_seq, tp->snd_nxt)) //回 RST 報文 goto reset_and_undo; ...}
從上面的函數,就可以得知了,客戶端在 SYN_SENT 狀態下,收到不正確的確認號的 syn+ack 報文會回 RST 報文。
小結TCP 三次握手中,客戶端收到的第二次握手中 ack 確認號不是自己期望的,會發生什么?是直接丟棄 or 回 RST 報文?
回 RST 報文。
什么情況下會收到不正確的 ack(第二次握手中的 ack) 呢?
當客戶端發起多次 SYN 報文,然后網絡擁堵的情況下,「舊的 SYN 報文」比「新的 SYN 報文」早抵達服務端,此時服務端就會按照收到的「舊的 SYN 報文」回復 syn+ack 報文,而此報文的確認號并不是客戶端期望收到的,于是客戶端就會回 RST 報文。
X 關閉
X 關閉
- 15G資費不大降!三大運營商誰提供的5G網速最快?中國信通院給出答案
- 2聯想拯救者Y70發布最新預告:售價2970元起 迄今最便宜的驍龍8+旗艦
- 3亞馬遜開始大規模推廣掌紋支付技術 顧客可使用“揮手付”結賬
- 4現代和起亞上半年出口20萬輛新能源汽車同比增長30.6%
- 5如何讓居民5分鐘使用到各種設施?沙特“線性城市”來了
- 6AMD實現連續8個季度的增長 季度營收首次突破60億美元利潤更是翻倍
- 7轉轉集團發布2022年二季度手機行情報告:二手市場“飄香”
- 8充電寶100Wh等于多少毫安?鐵路旅客禁止、限制攜帶和托運物品目錄
- 9好消息!京東與騰訊續簽三年戰略合作協議 加強技術創新與供應鏈服務
- 10名創優品擬通過香港IPO全球發售4100萬股 全球發售所得款項有什么用處?