為什么這么設(shè)計(Why's THE Design)是一系列關(guān)于計算機(jī)領(lǐng)域中程序設(shè)計決策的文章暖夭,我們在這個系列的每一篇文章中都會提出一個具體的問題并從不同的角度討論這種設(shè)計的優(yōu)缺點(diǎn)回还、對具體實(shí)現(xiàn)造成的影響厕氨。如果你有想要了解的問題驻售,可以在文章下面留言狸涌。
原文鏈接 https://draveness.me/whys-the-design-tcp-three-way-handshake
TCP 協(xié)議是我們幾乎每天都會接觸到的網(wǎng)絡(luò)協(xié)議果港,絕大多數(shù)網(wǎng)絡(luò)連接的建立都是基于 TCP 協(xié)議的廉羔,學(xué)過計算機(jī)網(wǎng)絡(luò)或者對 TCP 協(xié)議稍有了解的人都知道 —— 使用 TCP 協(xié)議建立連接需要經(jīng)過三次握手(three-way handshake)。
如果讓我們簡單說說 TCP 建立連接的過程塘揣,相信很多準(zhǔn)備過面試的人都會非常了解包雀,但是一旦想要深究『為什么 TCP 建立連接需要三次握手?』亲铡,作者相信大多數(shù)人都沒有辦法回答這個問題或者會給出錯誤的答案才写,這邊文章就會討論究竟為什么我們需要三次握手才能建立 TCP 連接?
需要注意的是我們會將重點(diǎn)放到為什么需要 TCP 建立連接需要『三次握手』奖蔓,而不僅僅是為什么需要『三次』握手琅摩。
概述
在具體分析今天的問題之前,我們首先可以了解一下最常見的錯誤類比锭硼,這個對 TCP 連接過程的錯誤比喻誤導(dǎo)了很多人,作者在比較長的一段時間內(nèi)也認(rèn)為它能夠很好地描述 TCP 建立連接為什么需要三次握手:
- 你聽得到嗎蜕劝?
- 我能聽到檀头,你聽得到轰异?
- 我也能聽到;
這種用類比來解釋問題往往就會面臨『十個類比九個錯』的尷尬局面暑始,如果別人用類比回答你的為什么搭独,你需要仔細(xì)想一想它的類比里究竟哪里有漏洞;類比帶來的解釋往往只能有片面的相似性廊镜,我們永遠(yuǎn)也無法找到絕對正確的類比牙肝,它只在我們想要通俗易懂地展示事物的特性時才能發(fā)揮較大的作用,我們在文章的后面會介紹為什么這里的類比有問題嗤朴,各位讀者也可以帶著疑問來閱讀剩下的內(nèi)容配椭。
很多人嘗試回答或者思考這個問題的時候其實(shí)關(guān)注點(diǎn)都放在了三次握手中的三次上面,這確實(shí)很重要雹姊,但是如果重新審視這個問題股缸,我們對于『什么是連接』真的清楚?只有知道連接的定義吱雏,我們才能去嘗試回答為什么 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 中的連接是什么,我們簡單總結(jié)一下:用于保證可靠性和流控制機(jī)制的信息歧杏,包括 Socket镰惦、序列號以及窗口大小叫做連接。
所以犬绒,建立 TCP 連接就是通信的雙方需要對上述的三種信息達(dá)成共識旺入,連接中的一對 Socket 是由互聯(lián)網(wǎng)地址標(biāo)志符和端口組成的,窗口大小主要用來做流控制懂更,最后的序列號是用來追蹤通信發(fā)起方發(fā)送的數(shù)據(jù)包序號眨业,接收方可以通過序列號向發(fā)送方確認(rèn)某個數(shù)據(jù)包的成功接收。
到這里沮协,我們將原有的問題轉(zhuǎn)換成了『為什么需要通過三次握手才可以初始化 Sockets龄捡、窗口大小和初始序列號?』慷暂,那么接下來我們就開始對這個細(xì)化的問題進(jìn)行分析并尋找解釋聘殖。
設(shè)計
這篇文章主要會從以下幾個方面介紹為什么我們需要通過三次握手才可以初始化 Sockets、窗口大小行瑞、初始序列號并建立 TCP 連接:
- 通過三次握手才能阻止重復(fù)歷史連接的初始化奸腺;
- 通過三次握手才能對通信雙方的初始序列號進(jìn)行初始化;
- 討論其他次數(shù)握手建立連接的可能性血久;
這幾個論點(diǎn)中的第一個是 TCP 選擇使用三次握手的最主要原因突照,其他的幾個原因相比之下都是次要的原因,我們在這里對它們的討論只是為了讓整個視角更加豐富氧吐,通過多方面理解這一有趣的設(shè)計決策讹蘑。
歷史連接
RFC 793 - Transmission Control Protocol 其實(shí)就指出了 TCP 連接使用三次握手的首要原因 —— 為了阻止歷史的重復(fù)連接初始化造成的混亂問題末盔,防止使用 TCP 協(xié)議通信的雙方建立了錯誤的連接。
The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion.
想象一下這個場景座慰,如果通信雙方的通信次數(shù)只有兩次陨舱,那么發(fā)送方一旦發(fā)出建立連接的請求之后它就沒有辦法撤回這一次請求,如果在網(wǎng)絡(luò)狀況復(fù)雜或者較差的網(wǎng)絡(luò)中版仔,發(fā)送方連續(xù)發(fā)送多次建立連接的請求游盲,如果 TCP 建立連接只能通信兩次,那么接收方只能選擇接受或者拒絕發(fā)送方發(fā)起的請求蛮粮,它并不清楚這一次請求是不是由于網(wǎng)絡(luò)擁堵而早早過期的連接益缎。
所以,TCP 選擇使用三次握手來建立連接并在連接引入了 RST
這一控制消息蝉揍,接收方當(dāng)收到請求時會將發(fā)送方發(fā)來的 SEQ+1
發(fā)送回接收方链峭,這時由發(fā)送方來判斷當(dāng)前連接是否是歷史連接:
- 如果當(dāng)前連接是歷史連接,即
SEQ
過期或者超時又沾,那么發(fā)送方就會直接發(fā)送RST
控制消息中止這一次連接弊仪; - 如果當(dāng)前連接不是歷史連接,那么發(fā)送方就會發(fā)送
ACK
控制消息杖刷,通信雙方就會成功建立連接励饵;
使用三次握手和 RST
控制消息將是否建立連接的最終控制權(quán)交給了發(fā)送方,因?yàn)橹挥邪l(fā)送方有足夠的上下文來判斷當(dāng)前連接是否是錯誤的或者過期的滑燃,這也是 TCP 使用三次握手建立連接的最主要原因役听。
初始序列號
另一個使用三次握手的重要的原因就是通信雙方都需要獲得一個用于發(fā)送信息的初始化序列號,作為一個可靠的傳輸層協(xié)議表窘,TCP 需要在不穩(wěn)定的網(wǎng)絡(luò)環(huán)境中構(gòu)建一個可靠的傳輸層典予,網(wǎng)絡(luò)的不確定性可能會導(dǎo)致數(shù)據(jù)包的缺失和順序顛倒等問題,常見的問題可能包括:
- 數(shù)據(jù)包被發(fā)送方多次發(fā)送造成數(shù)據(jù)的重復(fù)乐严;
- 數(shù)據(jù)包在傳輸?shù)倪^程中被路由或者其他節(jié)點(diǎn)丟失瘤袖;
- 數(shù)據(jù)包到達(dá)接收方可能無法按照發(fā)送順序;
為了解決上述這些可能存在的問題昂验,TCP 協(xié)議要求發(fā)送方在數(shù)據(jù)包中加入『序列號』字段捂敌,有了數(shù)據(jù)包對應(yīng)的序列號,我們就可以:
- 接收方可以通過序列號對重復(fù)的數(shù)據(jù)包進(jìn)行去重既琴;
- 發(fā)送方會在對應(yīng)數(shù)據(jù)包未被 ACK 時進(jìn)行重復(fù)發(fā)送占婉;
- 接收方可以根據(jù)數(shù)據(jù)包的序列號對它們進(jìn)行重新排序;
序列號在 TCP 連接中有著非常重要的作用甫恩,初始序列號作為 TCP 連接的一部分也需要在三次握手期間進(jìn)行初始化逆济,由于 TCP 連接通信的雙方都需要獲得初始序列號,所以它們其實(shí)需要向?qū)Ψ桨l(fā)送 SYN
控制消息并攜帶自己期望的初始化序列號 SEQ
,對方在收到 SYN
消息之后會通過 ACK
控制消息以及 SEQ+1
來進(jìn)行確認(rèn)奖慌。
如上圖所示霎终,通信雙方的兩個 TCP A/B
分別向?qū)Ψ桨l(fā)送 SYN
和 ACK
控制消息,等待通信雙方都獲取到了自己期望的初始化序列號之后就可以開始通信了升薯,由于 TCP 消息頭的設(shè)計,我們可以將中間的兩次通信合成一個击困,TCP B
可以向 TCP A
同時發(fā)送 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].
除此之外阅茶,網(wǎng)絡(luò)作為一個分布式的系統(tǒng)蛛枚,其中并不存在一個用于計數(shù)的全局時鐘,而 TCP 可以通過不同的機(jī)制來初始化序列號脸哀,作為 TCP 連接的接收方我們無法判斷對方傳來的初始化序列號是否過期蹦浦,所以我們需要交由對方來判斷,TCP 連接的發(fā)起方可以通過保存發(fā)出的序列號判斷連接是否過期撞蜂,如果讓接收方來保存并判斷序列號卻是不現(xiàn)實(shí)的盲镶,這也再一次強(qiáng)化了我們在上一節(jié)中提出的觀點(diǎn) —— 避免歷史錯連接的初始化。
通信次數(shù)
當(dāng)我們討論 TCP 建立連接需要的通信次數(shù)時蝌诡,我們經(jīng)常會執(zhí)著于為什么通信三次才可以建立連接溉贿,而不是兩次或者四次;討論使用更多的通信次數(shù)來建立連接往往是沒有意義的浦旱,因?yàn)槲覀兛偪梢?strong>使用更多的通信次數(shù)交換相同的信息宇色,所以使用四次、五次或者更多次數(shù)建立連接在技術(shù)上都是完全可以實(shí)現(xiàn)的颁湖。
這種增加 TCP 連接通信次數(shù)的問題往往沒有討論的必要性宣蠕,我們追求的其實(shí)是用更少的通信次數(shù)(理論上的邊界)完成信息的交換,也就是為什么我們在上兩節(jié)中也一再強(qiáng)調(diào)使用『兩次握手』沒有辦法建立 TCP 連接甥捺,使用三次握手是建立連接所需要的最小次數(shù)抢蚀。
總結(jié)
我們在這篇文章中討論了為什么 TCP 建立連接需要經(jīng)過三次握手,在具體分析這個問題之前涎永,我們首先重新思考了 TCP 連接究竟是什么思币,RFC 793 - Transmission Control Protocol - IETF Tools 對 TCP 連接有著非常清楚的定義 —— 用于保證可靠性和流控制機(jī)制的數(shù)據(jù),包括 Socket羡微、序列號以及窗口大小谷饿。
TCP 建立連接時通過三次握手可以有效地避免歷史錯誤連接的建立,減少通信雙方不必要的資源消耗妈倔,三次握手能夠幫助通信雙方獲取初始化序列號博投,它們能夠保證數(shù)據(jù)包傳輸?shù)牟恢夭粊G,還能保證它們的傳輸順序盯蝴,不會因?yàn)榫W(wǎng)絡(luò)傳輸?shù)膯栴}發(fā)生混亂毅哗,到這里不使用『兩次握手』和『四次握手』的原因已經(jīng)非常清楚了:
- 『兩次握手』:無法避免歷史錯誤連接的初始化听怕,浪費(fèi)接收方的資源;
- 『四次握手』:TCP 協(xié)議的設(shè)計可以讓我們同時傳遞
ACK
和SYN
兩個控制信息虑绵,減少了通信次數(shù)尿瞭,所以不需要使用更多的通信次數(shù)傳輸相同的信息;
我們重新回到在文章開頭提的問題翅睛,為什么使用類比解釋 TCP 使用三次握手是錯誤的声搁?這主要還是因?yàn)椋@個類比沒有解釋清楚核心問題 —— 避免歷史上的重復(fù)連接捕发。到最后疏旨,我們還是來看一些比較開放的相關(guān)問題,有興趣的讀者可以仔細(xì)想一下下面的問題:
- 除了使用序列號是否還有其他方式保證消息的不重不丟扎酷?
- UDP 協(xié)議有連接的概念么檐涝,它能保證數(shù)據(jù)傳輸?shù)目煽棵矗?/li>
如果對文章中的內(nèi)容有疑問或者想要了解更多軟件工程上一些設(shè)計決策背后的原因,可以在博客下面留言法挨,作者會及時回復(fù)本文相關(guān)的疑問并選擇其中合適的主題作為后續(xù)的內(nèi)容谁榜。