爲何這麼設計(Why's THE Design)是一系列關於計算機領域中程序設計決策的文章,咱們在這個系列的每一篇文章中都會提出一個具體的問題並從不一樣的角度討論這種設計的優缺點、對具體實現形成的影響。若是你有想要了解的問題,能夠在原文下面留言。原文連接:https://draveness.me/whys-the...html
TCP 協議是咱們幾乎天天都會接觸到的網絡協議,絕大多數網絡鏈接的創建都是基於 TCP 協議的,學過計算機網絡或者對 TCP 協議稍有了解的人都知道 —— 使用 TCP 協議創建鏈接須要通過三次握手(three-way handshake)。面試
若是讓咱們簡單說說 TCP 創建鏈接的過程,相信不少準備過面試的人都會很是瞭解,可是一旦想要深究『爲何 TCP 創建鏈接須要三次握手?』,做者相信大多數人都沒有辦法回答這個問題或者會給出錯誤的答案,這邊文章就會討論究竟爲何咱們須要三次握手才能創建 TCP 鏈接?網絡
須要注意的是咱們會將重點放到爲何須要 TCP 創建鏈接須要 『三次握手』,而 不只僅是爲何須要 『三次』握手。
在具體分析今天的問題以前,咱們首先能夠了解一下最多見的錯誤類比,這個對 TCP 鏈接過程的錯誤比喻誤導了不少人,做者在比較長的一段時間內也認爲它可以很好地描述 TCP 創建鏈接爲何須要三次握手:less
這種用類比來解釋問題每每就會面臨『十個類比九個錯』的尷尬局面,若是別人用類比回答你的爲何,你須要仔細想想它的類比裏究竟哪裏有漏洞;類比帶來的解釋每每只能有片面的類似性,咱們永遠也沒法找到絕對正確的類比,它只在咱們想要通俗易懂地展現事物的特性時才能發揮較大的做用,咱們在文章的後面會介紹爲何這裏的類比有問題,各位讀者也能夠帶着疑問來閱讀剩下的內容。socket
不少人嘗試回答或者思考這個問題的時候其實關注點都放在了三次握手中的三次上面,這確實很重要,可是若是從新審視這個問題,咱們對於『什麼是鏈接』真的清楚?只有知道鏈接的定義,咱們才能去嘗試回答爲何 TCP 創建鏈接須要三次握手。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 鏈接就是通訊的雙方須要對上述的三種信息達成共識,鏈接中的一對 Socket 是由互聯網地址標誌符和端口組成的,窗口大小主要用來作流控制,最後的序列號是用來追蹤通訊發起方發送的數據包序號,接收方能夠經過序列號向發送方確認某個數據包的成功接收。ui
到這裏,咱們將原有的問題轉換成了『爲何須要經過三次握手才能夠初始化 Sockets、窗口大小和初始序列號?』,那麼接下來咱們就開始對這個細化的問題進行分析並尋找解釋。this
這篇文章主要會從如下幾個方面介紹爲何咱們須要經過三次握手才能夠初始化 Sockets、窗口大小、初始序列號並創建 TCP 鏈接:spa
這幾個論點中的第一個是 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 創建鏈接只能通訊兩次,那麼接收方只能選擇接受或者拒絕發送方發起的請求,它並不清楚這一次請求是否是因爲網絡擁堵而早早過時的鏈接。
因此,TCP 選擇使用三次握手來創建鏈接並在鏈接引入了 RST
這一控制消息,接收方當收到請求時會將發送方發來的 SEQ+1
發送回接收方,這時由發送方來判斷當前鏈接是不是歷史鏈接:
SEQ
過時或者超時,那麼發送方就會直接發送 RST
控制消息停止這一次鏈接;ACK
控制消息,通訊雙方就會成功創建鏈接;使用三次握手和 RST
控制消息將是否創建鏈接的最終控制權交給了發送方,由於只有發送方有足夠的上下文來判斷當前鏈接是不是錯誤的或者過時的,這也是 TCP 使用三次握手創建鏈接的最主要緣由。
另外一個使用三次握手的重要的緣由就是通訊雙方都須要得到一個用於發送信息的初始化序列號,做爲一個可靠的傳輸層協議,TCP 須要在不穩定的網絡環境中構建一個可靠的傳輸層,網絡的不肯定性可能會致使數據包的缺失和順序顛倒等問題,常見的問題可能包括:
爲了解決上述這些可能存在的問題,TCP 協議要求發送方在數據包中加入『序列號』字段,有了數據包對應的序列號,咱們就能夠:
序列號在 TCP 鏈接中有着很是重要的做用,初始序列號做爲 TCP 鏈接的一部分也須要在三次握手期間進行初始化,因爲 TCP 鏈接通訊的雙方都須要得到初始序列號,因此它們其實須要向對方發送 SYN
控制消息並攜帶本身指望的初始化序列號 SEQ
,對方在收到 SYN
消息以後會經過 ACK
控制消息以及 SEQ+1
來進行確認。
如上圖所示,通訊雙方的兩個 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 鏈接通訊次數的問題每每沒有討論的必要性,咱們追求的實際上是用更少的通訊次數(理論上的邊界)完成信息的交換,也就是爲何咱們在上兩節中也一再強調使用『兩次握手』沒有辦法創建 TCP 鏈接,使用三次握手是創建鏈接所須要的最小次數。
咱們在這篇文章中討論了爲何 TCP 創建鏈接須要通過三次握手,在具體分析這個問題以前,咱們首先從新思考了 TCP 鏈接到底是什麼,RFC 793 - Transmission Control Protocol - IETF Tools 對 TCP 鏈接有着很是清楚的定義 —— 用於保證可靠性和流控制機制的數據,包括 Socket、序列號以及窗口大小。
TCP 創建鏈接時經過三次握手能夠有效地避免歷史錯誤鏈接的創建,減小通訊雙方沒必要要的資源消耗,三次握手可以幫助通訊雙方獲取初始化序列號,它們可以保證數據包傳輸的不重不丟,還能保證它們的傳輸順序,不會由於網絡傳輸的問題發生混亂,到這裏不使用『兩次握手』和『四次握手』的緣由已經很是清楚了:
ACK
和 SYN
兩個控制信息,減小了通訊次數,因此不須要使用更多的通訊次數傳輸相同的信息;咱們從新回到在文章開頭提的問題,爲何使用類比解釋 TCP 使用三次握手是錯誤的?這主要仍是由於,這個類比沒有解釋清楚核心問題 —— 避免歷史上的重複鏈接。到最後,咱們仍是來看一些比較開放的相關問題,有興趣的讀者能夠仔細想一下下面的問題:
若是對文章中的內容有疑問或者想要了解更多軟件工程上一些設計決策背後的緣由,能夠在博客下面留言,做者會及時回覆本文相關的疑問並選擇其中合適的主題做爲後續的內容。