三次握手的誤解與錯誤類比 (RFC 解讀)

YongHaoHu發表於2019-01-11

來自我的公眾號 『 YongHao 寫東西的 Cache 』 打個小廣告,還是希望寫的東西有人看?

分享一下見解,權當拋磚引玉

三次握手的誤解與錯誤類比(RFC解讀)

關於TCP三次握手幾乎是應屆畢業生面試常見的問題了,然而網上還很多比比皆是的錯誤,以知乎 TCP 為什麼是三次握手,而不是兩次或四次? 上的熱門答案為例子,第一個3.6K 次贊同的類比就是錯誤的:

三次握手:
“喂,你聽得到嗎?”
“我聽得到呀,你聽得到我嗎?”
“我能聽到你,今天 balabala……”
複製程式碼

同樣這個107次贊同的類比也是錯誤的:

握手和敬軍禮一樣,源自「敵我雙方互相確認對方手裡沒有武器、無惡意」的儀式。(雖然雙方互相請求確認需要四步,但由於中間的確認和請求是由同一個人執行的,所以合併成了一步) 
正恩伸出手說:你看,我手裡沒有武器。(SYN) 
朗普看了看說:嗯,確實沒有。(ACK)
於是也伸出手說:你看,我手裡也沒有武器。(SYN)
正恩看了看說:嗯,看來你確實有誠意。(ACK)
複製程式碼

這兩個類比就是想當然的錯誤,為什麼會錯誤,看完全文相信你便了然於心。

另外還有一個就是在謝希仁著《計算機網路》第四版中,講 “三次握手” 的目的是 “為了防止已失效的連線請求報文段突然又傳送到了服務端,因而產生錯誤”,這個只能算是表因,並不涉及本質。

謝希仁版《計算機網路》中的例子是這樣的,“已失效的連線請求報文段” 的產生在這樣一種情況下:client 發出的第一個連線請求報文段並沒有丟失,而是在某個網路結點長時間的滯留了,以致延誤到連線釋放以後的某個時間才到達 server。本來這是一個早已失效的報文段。但 server 收到此失效的連線請求報文段後,就誤認為是 client 再次發出的一個新的連線請求。於是就向 client 發出確認報文段,同意建立連線。假設不採用 “三次握手”,那麼只要 server 發出確認,新的連線就建立了。由於現在 client 並沒有發出建立連線的請求,因此不會理睬 server 的確認,也不會向 server 傳送資料。但 server 卻以為新的運輸連線已經建立,並一直等待 client 發來資料。這樣,server 的很多資源就白白浪費掉了。採用 “三次握手” 的辦法可以防止上述現象發生。例如剛才那種情況,client 不會向 server 的確認發出確認。server 由於收不到確認,就知道 client 並沒有要求建立連線。”

如果你細讀RFC793,也就是 TCP 的協議 RFC,你就會發現裡面就講到了為什麼三次握手是必須的——TCP 需要 seq 序列號來做可靠重傳或接收,而避免連線複用時無法分辨出 seq 是延遲或者是舊連結的 seq,因此需要三次握手來約定確定雙方的 ISN(初始 seq 序列號)。

下面給出詳細的 RFC 解讀說明:(資料分組稱為分段(Segment),國內通常用包來稱呼)


我們首先要知道到一點就是, TCP 的可靠連線是靠 seq( sequence numbers 序列號)來達成的。

A fundamental notion in the design is that every octet of data sent over a TCP connection has a sequence number. Since every octet is sequenced, each of them can be acknowledged.
The acknowledgment mechanism employed is cumulative so that an acknowledgment of sequence number X indicates that all octets up to but not including X have been received.

TCP 設計中一個基本設定就是,通過TCP 連線傳送的每一個包,都有一個sequence number。而因為每個包都是有序列號的,所以都能被確認收到這些包。

確認機制是累計的,所以一個對sequence number X 的確認,意味著 X 序列號之前(不包括 X) 包都是被確認接收到的。

The protocol places no restriction on a particular connection being used over and over again.

The problem that arises from this is -- "how does the TCP identify duplicate segments from previous incarnations of the connection?" This problem becomes apparent if the connection is being opened and closed in quick succession, or if the connection breaks with loss of memory and is then reestablished.

TCP 協議是不限制一個特定的連線(兩端 socket 一樣)被重複使用的。

所以這樣就有一個問題:這條連線突然斷開重連後,TCP 怎麼樣識別之前舊連結重發的包?——這就需要獨一無二的 ISN(初始序列號)機制。

When new connections are created, an initial sequence number (ISN) generator is employed which selects a new 32 bit ISN. The generator is bound to a (possibly fictitious) 32 bit clock whose low order bit is incremented roughly every 4 microseconds. Thus, the ISN cycles approximately every 4.55 hours. Since we assume that segments will stay in the network no more than the Maximum Segment Lifetime (MSL) and that the MSL is less than 4.55 hours we can reasonably assume that ISN's will be unique.

當一個新連線建立時,初始序列號( initial sequence number ISN)生成器會生成一個新的32位的 ISN。

這個生成器會用一個32位長的時鐘,差不多4µs 增長一次,因此 ISN 會在大約 4.55 小時迴圈一次

2^32位的計數器,需要2^32*4 µs才能自增完,除以1小時共有多少µs便可算出2^32*4 /(1*60*60*1000*1000)=4.772185884

而一個段在網路中並不會比最大分段壽命(Maximum Segment Lifetime (MSL) ,預設使用2分鐘)長,MSL 比4.55小時要短,所以我們可以認為 ISN 會是唯一的。

傳送方與接收方都會有自己的 ISN (下面的例子中就是 X 與 Y)來做雙方互發通訊,具體的描述如下:

  1. A --> B SYN my sequence number is X
  2. A <-- B ACK your sequence number is X
  3. A <-- B SYN my sequence number is Y
  4. A --> B ACK your sequence number is Y

2與3都是 B 傳送給 A,因此可以合併在一起,因此成為three way (or three message) handshake(其實翻譯為三步握手,或者是三次通訊握手更為準確)

因此最終可以得出,三次握手是必須的:

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].

三次握手(A three way handshake)是必須的, 因為 sequence numbers(序列號)沒有繫結到整個網路的全域性時鐘(全部統一使用一個時鐘,就可以確定這個包是不是延遲到的)以及 TCPs 可能有不同的機制來選擇 ISN(初始序列號)。

接收方接收到第一個 SYN 時,沒有辦法知道這個 SYN 是是否延遲了很久了,除非他有辦法記住在這條連線中,最後接收到的那個sequence numbers(然而這不總是可行的)。

這句話的意思是:一個 seq 過來了,跟現在記住的 seq 不一樣,我怎麼知道他是上條延遲的,還是上上條延遲的呢?

所以,接收方一定需要跟傳送方確認 SYN。

假設不確認 SYN 中的 SEQ,那麼就只有:

  1. A --> B SYN my sequence number is X
  2. A <-- B ACK your sequence number is X SYN my sequence number is Y

只有B確認了收到了 A 的 SEQ, A 無法確認收到 B 的。也就是說,只有 A 傳送給 B 的包都是可靠的, 而 B 傳送給 A 的則不是,所以這不是可靠的連線。這種情況如果只需要 A 傳送給 B ,B 無需迴應,則可以不做三次握手。

三次握手詳細過程

      TCP A                                                TCP B

  1.  CLOSED                                               LISTEN

  2.  SYN-SENT    --> <SEQ=100><CTL=SYN>               --> SYN-RECEIVED

  3.  ESTABLISHED <-- <SEQ=300><ACK=101><CTL=SYN,ACK>  <-- SYN-RECEIVED

  4.  ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK>       --> ESTABLISHED

  5.  ESTABLISHED --> <SEQ=101><ACK=301><CTL=ACK><DATA> --> ESTABLISHED

          Basic 3-Way Handshake for Connection Synchronization

                                Figure 7.
複製程式碼

在上圖

  • 第二行中, A 傳送了 SEQ 100,標誌位是 SYN;
  • 第三行,B 發回了 ACK 101 與 SEQ 300,標誌位是 SYN 與 ACK(兩個過程合併了)。注意,ACK 是101意味著,B 希望接收到 101序列號開始的資料段。
  • 第四行,A 返回了空的資料,SEQ 101, ACK 301,標誌位為 ACK。至此,雙方的開始 SEQ (也就是 ISN)號100與300都被確認接收到了。
  • 第五行,開始正式傳送資料包,注意的是 ACK 依舊是第四行的301,因為沒有需要 ACK 的 SYN 了(第四行已經 ACK 完)。

相關文章