為什麼 TCP 建立連接需要三次握手 · Why's THE Design?

為什麼這麼設計(Why’s THE Design)是一系列關於計算機領域中程序設計決策的文章,我們在這個系列的每一篇文章中都會提出一個具體的問題並從不同的角度討論這種設計的優缺點、對具體實現造成的影響。如果你有想要了解的問題,可以在文章下面留言。

TCP 協議是我們幾乎每天都會接觸到的網絡協議,絕大多數網絡連接的建立都是基於 TCP 協議的,學過計算機網絡或者對 TCP 協議稍有了解的人都知道 —— 使用 TCP 協議建立連接需要經過三次握手(three-way handshake)。

如果讓我們簡單說說 TCP 建立連接的過程,相信很多準備過面試的人都會非常瞭解,但是一旦想要深究『為什麼 TCP 建立連接需要三次握手?』,作者相信大多數人都沒有辦法回答這個問題或者會給出錯誤的答案,這邊文章就會討論究竟為什麼我們需要三次握手才能建立 TCP 連接?

需要注意的是我們會將重點放到為什麼需要 TCP 建立連接需要『三次握手』,而不僅僅是為什麼需要『三次』握手。

概述

在具體分析今天的問題之前,我們首先可以瞭解一下最常見的錯誤類比,這個對 TCP 連接過程的錯誤比喻誤導了很多人,作者在比較長的一段時間內也認為它能夠很好地描述 TCP 建立連接為什麼需要三次握手:

  1. 你聽得到嗎?
  2. 我能聽到,你聽得到?
  3. 我也能聽到;

這種用類比來解釋問題往往就會面臨『十個類比九個錯』的尷尬局面,如果別人用類比回答你的為什麼,你需要仔細想一想它的類比裡究竟哪裡有漏洞;類比帶來的解釋往往只能有片面的相似性,我們永遠也無法找到絕對正確的類比,它只在我們想要通俗易懂地展示事物的特性時才能發揮較大的作用,我們在文章的後面會介紹為什麼這裡的類比有問題,各位讀者也可以帶著疑問來閱讀剩下的內容。

很多人嘗試回答或者思考這個問題的時候其實關注點都放在了三次握手中的三次上面,這確實很重要,但是如果重新審視這個問題,我們對於『什麼是連接』真的清楚?只有知道連接的定義,我們才能去嘗試回答為什麼 TCP 建立連接需要三次握手。

The reliability and flow control mechanisms described above require that TCPs initialize and maintain certain status information for each data stream. The combination of this information, including sockets, sequence numbers, and window sizes, is called a connection.

RFC 793 - Transmission Control Protocol 文檔中非常清楚地定義了 TCP 中的連接是什麼,我們簡單總結一下:用於保證可靠性和流控制機制的信息,包括 Socket、序列號以及窗口大小叫做連接。

為什麼 TCP 建立連接需要三次握手 · Why's THE Design?

所以,建立 TCP 連接就是通信的雙方需要對上述的三種信息達成共識,連接中的一對 Socket 是由互聯網地址標誌符和端口組成的,窗口大小主要用來做流控制,最後的序列號是用來追蹤通信發起方發送的數據包序號,接收方可以通過序列號向發送方確認某個數據包的成功接收。

到這裡,我們將原有的問題轉換成了『為什麼需要通過三次握手才可以初始化 Sockets、窗口大小和初始序列號?』,那麼接下來我們就開始對這個細化的問題進行分析並尋找解釋。

設計

這篇文章主要會從以下幾個方面介紹為什麼我們需要通過三次握手才可以初始化 Sockets、窗口大小、初始序列號並建立 TCP 連接:

  • 通過三次握手才能阻止重複歷史連接的初始化;
  • 通過三次握手才能對通信雙方的初始序列號進行初始化;
  • 討論其他次數握手建立連接的可能性;

這幾個論點中的第一個是 TCP 選擇使用三次握手的最主要原因,其他的幾個原因相比之下都是次要的原因,我們在這裡對它們的討論只是為了讓整個視角更加豐富,通過多方面理解這一有趣的設計決策。

歷史連接

RFC 793 - Transmission Control Protocol 其實就指出了 TCP 連接使用三次握手的首要原因 —— 為了阻止歷史的重複連接初始化造成的混亂問題,防止使用 TCP 協議通信的雙方建立了錯誤的連接。

The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.

為什麼 TCP 建立連接需要三次握手 · Why's THE Design?

想象一下這個場景,如果通信雙方的通信次數只有兩次,那麼發送方一旦發出建立連接的請求之後它就沒有辦法撤回這一次請求,如果在網絡狀況複雜或者較差的網絡中,發送方連續發送多次建立連接的請求,如果 TCP 建立連接只能通信兩次,那麼接收方只能選擇接受或者拒絕發送方發起的請求,它並不清楚這一次請求是不是由於網絡擁堵而早早過期的連接。

所以,TCP 選擇使用三次握手來建立連接並在連接引入了 RST 這一控制消息,接收方當收到請求時會將發送方發來的 SEQ+1 發送回接收方,這時由發送方來判斷當前連接是否是歷史連接:

  • 如果當前連接是歷史連接,即 SEQ 過期或者超時,那麼發送方就會直接發送 RST 控制消息中止這一次連接;
  • 如果當前連接不是歷史連接,那麼發送方就會發送 ACK 控制消息,通信雙方就會成功建立連接;

使用三次握手和 RST 控制消息將是否建立連接的最終控制權交給了發送方,因為只有發送方有足夠的上下文來判斷當前連接是否是錯誤的或者過期的,這也是 TCP 使用三次握手建立連接的最主要原因。

初始序列號

另一個使用三次握手的重要的原因就是通信雙方都需要獲得一個用於發送信息的初始化序列號,作為一個可靠的傳輸層協議,TCP 需要在不穩定的網絡環境中構建一個可靠的傳輸層,網絡的不確定性可能會導致數據包的缺失和順序顛倒等問題,常見的問題可能包括:

  • 數據包被髮送方多次發送造成數據的重複;
  • 數據包在傳輸的過程中被路由或者其他節點丟失;
  • 數據包到達接收方可能無法按照發送順序;

為了解決上述這些可能存在的問題,TCP 協議要求發送方在數據包中加入『序列號』字段,有了數據包對應的序列號,我們就可以:

  • 接收方可以通過序列號對重複的數據包進行去重;
  • 發送方會在對應數據包未被 ACK 時進行重複發送;
  • 接收方可以根據數據包的序列號對它們進行重新排序;

序列號在 TCP 連接中有著非常重要的作用,初始序列號作為 TCP 連接的一部分也需要在三次握手期間進行初始化,由於 TCP 連接通信的雙方都需要獲得初始序列號,所以它們其實需要向對方發送 SYN 控制消息並攜帶自己期望的初始化序列號 SEQ,對方在收到 SYN 消息之後會通過 ACK 控制消息以及 SEQ+1 來進行確認。

為什麼 TCP 建立連接需要三次握手 · Why's THE Design?

如上圖所示,通信雙方的兩個 TCP A/B 分別向對方發送 SYN 和 ACK 控制消息,等待通信雙方都獲取到了自己期望的初始化序列號之後就可以開始通信了,由於 TCP 消息頭的設計,我們可以將中間的兩次通信合成一個,TCP B 可以向 TCP A 同時發送 ACK 和 SYN 控制消息,這也就幫助我們將四次通信減少至三次。

A three way handshake is necessary because sequence numbers are not tied to a global clock in the network, and TCPs may have different mechanisms for picking the ISN’s. The receiver of the first SYN has no way of knowing whether the segment was an old delayed one or not, unless it remembers the last sequence number used on the connection (which is not always possible), and so it must ask the sender to verify this SYN. The three way handshake and the advantages of a clock-driven scheme are discussed in [3].

除此之外,網絡作為一個分佈式的系統,其中並不存在一個用於計數的全局時鐘,而 TCP 可以通過不同的機制來初始化序列號,作為 TCP 連接的接收方我們無法判斷對方傳來的初始化序列號是否過期,所以我們需要交由對方來判斷,TCP 連接的發起方可以通過保存發出的序列號判斷連接是否過期,如果讓接收方來保存並判斷序列號卻是不現實的,這也再一次強化了我們在上一節中提出的觀點 —— 避免歷史錯連接的初始化。

通信次數

當我們討論 TCP 建立連接需要的通信次數時,我們經常會執著於為什麼通信三次才可以建立連接,而不是兩次或者四次;討論使用更多的通信次數來建立連接往往是沒有意義的,因為我們總可以使用更多的通信次數交換相同的信息,所以使用四次、五次或者更多次數建立連接在技術上都是完全可以實現的。

為什麼 TCP 建立連接需要三次握手 · Why's THE Design?

這種增加 TCP 連接通信次數的問題往往沒有討論的必要性,我們追求的其實是用更少的通信次數(理論上的邊界)完成信息的交換,也就是為什麼我們在上兩節中也一再強調使用『兩次握手』沒有辦法建立 TCP 連接,使用三次握手是建立連接所需要的最小次數。

總結

我們在這篇文章中討論了為什麼 TCP 建立連接需要經過三次握手,在具體分析這個問題之前,我們首先重新思考了 TCP 連接究竟是什麼,RFC 793 - Transmission Control Protocol - IETF Tools 對 TCP 連接有著非常清楚的定義 —— 用於保證可靠性和流控制機制的數據,包括 Socket、序列號以及窗口大小。

TCP 建立連接時通過三次握手可以有效地避免歷史錯誤連接的建立,減少通信雙方不必要的資源消耗,三次握手能夠幫助通信雙方獲取初始化序列號,它們能夠保證數據包傳輸的不重不丟,還能保證它們的傳輸順序,不會因為網絡傳輸的問題發生混亂,到這裡不使用『兩次握手』和『四次握手』的原因已經非常清楚了:

  • 『兩次握手』:無法避免歷史錯誤連接的初始化,浪費接收方的資源;
  • 『四次握手』:TCP 協議的設計可以讓我們同時傳遞 ACK 和 SYN 兩個控制信息,減少了通信次數,所以不需要使用更多的通信次數傳輸相同的信息;

我們重新回到在文章開頭提的問題,為什麼使用類比解釋 TCP 使用三次握手是錯誤的?這主要還是因為,這個類比沒有解釋清楚核心問題 —— 避免歷史上的重複連接。到最後,我們還是來看一些比較開放的相關問題,有興趣的讀者可以仔細想一下下面的問題:

  • 除了使用序列號是否還有其他方式保證消息的不重不丟?
  • UDP 協議有連接的概念麼,它能保證數據傳輸的可靠麼?

如果對文章中的內容有疑問或者想要了解更多軟件工程上一些設計決策背後的原因,可以在博客下面留言,作者會及時回覆本文相關的疑問並選擇其中合適的主題作為後續的內容。

Reference

  • RFC 793 - Transmission Control Protocol - IETF Tools
  • Why do we need a 3-way handshake? Why not just 2-way?

原文鏈接: https://draveness.me/whys-the-design-tcp-three-way-handshake


分享到:


相關文章: