初始狀態(tài)時,TCP A 處于連接關(guān)閉狀態(tài), TCP B 處于監(jiān)聽狀態(tài). 也就是通常所說的 A 時 TCP 客戶段,B 是服務(wù)端.
A 發(fā)送 SYN 給 B, 并附有 SEQ, 請求建立 TCP 連接。 A 發(fā)送 SYN 后,狀態(tài)切換為 SYN-SENT, B 接收到 A 發(fā)送的 SYN 后狀態(tài)切換為 SYN-RECEIVED.
B 收到 A 的 SYN 之后,發(fā)送它的 SYN(注意,這里 A 和 B 的 SEQ 是相互獨(dú)立的 ),并附上 ACK 標(biāo)記用以表明 B 收到了 A 的 SYN 包。 這里注意: B 發(fā)送的 ACK 的值為 101,它代表 B 收到了序列號為 100和100之前的所有字節(jié)數(shù)據(jù),并告訴 A 自己期待下一次收到序列號以101開始的數(shù)據(jù). A 在接收到 B 的 SYN 之后,狀態(tài)轉(zhuǎn)化為 ESTABLISHED.
A 收到 B 的 SYN 之后, 需要發(fā)送 ACK 給 B 告訴 B 自己收到了它的 SYN + ACK 包. 這里注意:A 發(fā)送的ACK的值為 301, 原因是 B 的 SYN 中的 SEQ 是 300. A 發(fā)送的 SEQ 是 101,原因是上一次的請求中序列號已經(jīng)增長到了 100. 下一個可用的序列號就是 101. 在B接收到 A 的 ACK 之后,它的狀態(tài)切換為 ESTABLSHED. 至此,三次握手已經(jīng)完成,一個 TCP 連接已經(jīng)成功建立。
注意,這里圖中雖然對數(shù)據(jù)包進(jìn)行 1-7 的編號,但是對于雙方任何一方,都是獨(dú)立的. 也就是說,接收數(shù)據(jù)的先后是不確定的,有可能 B 先接收到 A 的包,也有可能 A 先接受到 B 的包。 這里我們并沒有明確的指定哪一方為 服務(wù)端,哪一方為客戶端,因此這里的結(jié)論都是成立的.
初始狀態(tài)下,雙方均處于 CLOSED 狀態(tài). 存在這種可能: 某一時刻雙發(fā)同時發(fā)送自己的 SYN 給對方,請求建立一條 TCP 連接.
在某一時間點(diǎn),A 發(fā)送 SYN 到 B 請求建立 TCP 連接. 此時,A 的狀態(tài)切換為 SYN-SENT
B 在未收到 A 發(fā)送的 SYN 包時,發(fā)送了自己的 SYN 給 A,請求建立一條連接. 此時,B 的狀態(tài)也切換為 SYN-SENT. 在接收到 B 發(fā)送的 SYN 之后,A 的狀態(tài)切換為 SYN-RECEIVED.
此時,A 發(fā)送的 SYN 到達(dá) B 端。B 收到了 A 發(fā)送的 SYN 包,狀態(tài)切換為 SYN-RECEIVED.
在第三步之后,A的狀態(tài)一旦變成SYN-RECEIVED, 他就需要發(fā)送對應(yīng)的 SYN+ACK 給 B,以確認(rèn)自己接收到了 B 發(fā)送的 SYN. 并將自己的狀態(tài)切換為 ESTABLISHED. 這里注意,它發(fā)送完 SYN+ACK 之后,只是單方的進(jìn)入 ESTABLISHED 狀態(tài),對應(yīng)狀態(tài)依然為 SYN-RECEIVED.
RFC793 原文: The principle reason for the three-way handshake is to prevent old duplicate connection initiations from causing confusion
這里我們就來看看它是如何處理的。
看下圖的 TCP 包流程:
初始狀態(tài)下, A 為客戶端,處于 CLOSED 狀態(tài)。 B 為服務(wù)端,處于LISTEN狀態(tài)
某一時刻,A 發(fā)送 SYN 給 B,請求建立 TCP 連接
B 在收到 A 剛剛發(fā)送的 SYN 包之前, 收到了一個舊的重復(fù)的來自 A 的 SYN.
對于 B 段來說,在收到來自 A 的 SYN 包時,它是不知道那是一個舊的重復(fù)的 SYN 包的,因此它就想就受到一個普通的 SYN一樣響應(yīng)這個 SYN 包. 發(fā)送 SYN+ACK 進(jìn)行確認(rèn).
A 端收到來自 B 段的 SYN+ACK, 發(fā)現(xiàn)其中的 ACK 字段的值不正確. 因?yàn)樽约喊l(fā)送的 SYN 中 SEQ 值為 100,響應(yīng)SYN+ACK包中的 ACK的值應(yīng)該為 101,但是它收到的 SYN+ACK包中的ACK的值卻是91. 在收到這個非法的SYN+ACK之后,A 段發(fā)送 RST,并附上錯誤的SEQ序列號. B 端收到A發(fā)送過來的 RST 之后,便得知 A 重置了上一個SYN想要建立的TCP連接,B 段重置所有關(guān)于為上一個 SYN 所記錄的狀態(tài),并重新回到 LISTEN 狀態(tài).
對于 TCP 協(xié)議來說,數(shù)據(jù)是不會丟失的,也就是說 B 段遲早會收到 A 發(fā)送的 SYN(SEQ=100). B 端收到 A 的 RST 之后,狀態(tài)切換到了 LISTEN之后, A 發(fā)送 SYN(SEQ=100)到達(dá),此時B依然回像通常情況一樣. 如果 SYN(SEQ=100)到達(dá) B 段早于 A 發(fā)送的 RST,將會是一個更復(fù)雜的情況,涉及到雙發(fā)均發(fā)送 RST 的場景. 這里能力有限,略掉.
什么是半連接狀態(tài)的鏈接? B方鏈接已經(jīng)關(guān)閉或者由于其他原因而奔潰掉,但是A方不知道B方的情況,此時 A 方持有的就是一個半連接狀態(tài)的鏈接.
我們通過一個例子來描述半連接狀態(tài)的鏈接
在某一時刻,A段的鏈接崩潰掉。假設(shè)奔潰的原因是A端物理內(nèi)存不足. B 端未收到任何通知
在A端從錯誤中回復(fù)過來之后,(假設(shè))用戶嘗試發(fā)送數(shù)據(jù)從原先的 TCP 連接上,A 端TCP模塊會返回類似的 “connection not open”錯誤,此時 A 端TCP狀態(tài)為 CLOSED, B 端不知道 A 端的情況,因此狀態(tài)依然為 ESTABLISHED.
A 端發(fā)現(xiàn) TCP 錯誤后,嘗試重新建立原先的 TCP 鏈接,發(fā)送SYN到 B 端
B 端收到 A 的 SYN 之后,發(fā)現(xiàn)收到的 SEQ(400) 于自己期待的 SEQ(100) 無法匹配, 因此不對這個 SYN 進(jìn)行確認(rèn),而是發(fā)送自已認(rèn)為正確的 SEQ(100) 給 A 端.
A 端收到 B 端發(fā)送的 SYN+ACK 之后,發(fā)現(xiàn)收到的 SEQ(100) 與自己剛剛發(fā)送的 SYN 中的 SEQ(400)不一致,因此得知自己的TCP狀態(tài)已經(jīng)與B端不一致了。因此,發(fā)送 RST 重置當(dāng)前的 TCP 鏈接.