TCP
建立連接和斷開連接很重要,但是自己又經常忘記一些具體細節,回顧的時候要麼到收藏夾裏找文章,要麼是百度/谷歌一波,偶爾一兩次還好,次數多了略顯麻煩,遂趁這次溫故的機會,記錄下來,方便自己也方便他人~
TCP報文格式
TCP
報文格式如下圖所示:
其中有幾個字段比較重要,在下面講三次握手和四次揮手是會用到:
- 序號:
seq
序號,佔32位
,用來標識從TCP
源端向目的端發送的字節流,發起方發送數據時對此進行標記; - 確認序號:
ack
序號,佔32位
,只有ACK
標誌位爲1時,確認序號字段纔有效; - 標誌位:共
6
個,即URG
、ACK
、PSH
、RST
、SYN
、FIN
,每個標誌位表示一個控制空能,具體如下:URG
:緊急指針(urgent pointer
),爲1
時表示緊急指針有效,爲0
則忽略緊急指針。緊急指針是一個正的偏移量,和序號字段中的值相加表示緊急數據最後一個字節的序號。TCP
的緊急方式是發送端向另一端發送緊急數據的一種方式;ACK
:確認序號,爲1
時表示確認號有效,爲0
表示報文中不含確認信息,忽略確認號字段;PSH
:PUSH
標誌,爲1
表示是帶有PUSH
標誌的數據,指示接收方在接收到該報文段以後,應儘快將這個報文段交給應用程序,而不是在緩衝區排隊;RST
:重置/復位連接,用於重置由於主機崩潰或其他原因而出現錯誤的連接。或者用於拒絕非法的報文段和拒絕連接請求;SYN
:同步序號,用於建立連接過程;FIN
:FINISH
標誌,釋放連接,爲1
時表示發送方已經沒有數據發送了,即關閉本方數據流。
需要特別注意:
- 確認序號
ack
與標誌位ACK
不同,不能混淆,看下面就會懂;- 確認方的確認序號
ack
等於發起方的序號seq
加1
,即ack=seq+1
,才能夠兩端匹配。
三次握手(建立TCP連接)
三次握手(Three-Way Handshake
)即建立TCP
連接,指建立一個TCP
連接時,需要客戶端和服務端總共發送3
個包來確認連接的建立,整個過程大致如下:
- 第一次握手:
Client
將標誌位SYN
置爲1
,隨機產生一個值seq=J
,並將該數據包發送給Server
,Client
進入SYN_SENT
狀態,等待Server
確認。 - 第二次握手:
Server
收到數據包後由標誌位SYN=1
知道Client
請求建立連接,Server
將標誌位SYN
和ACK
都置爲1
,ack=J+1
,隨機產生一個值seq=K
,並將該數據包發送給Client
以確認連接請求,Server
進入SYN_RCVD
狀態。 - 第三次握手:
Client
收到確認後,檢查ack
是否爲J+1
,ACK
是否爲1
,如果正確則將標誌位ACK
置爲1
,ack=K+1
,並將該數據包發送給Server
,Server
檢查ack
是否爲K+1
,ACK
是否爲1
,如果正確則連接建立成功,Client
和Server
進入ESTABLISHED
狀態,完成三次握手,隨後Client
與Server
之間可以開始傳輸數據了。
半連接(half-open connect)
在三次握手過程中,第二次握手,即Server
發送SYN-ACK
之後,收到Client
的ACK
之前的TCP
連接被稱爲半連接。
此時Server
會將第一次握手相關信息放入半連接隊列(syns queue
)中,若收到第三次握手Client
發送的ACK
報文後,則將相關信息挪入全連接隊列(accept queue
),否則會有限次數(超時機制)的重傳SYN-ACK
。如果半連接隊列或者全連接隊列已滿,則拋棄此次連接。
SYN攻擊(半連接攻擊):
惡意Client
在短時間內僞造大量不存在的IP
地址,並向Server
不斷地發送SYN
包,Server
回覆確認包,並等待Client
的確認,由於源地址是不存在的,因此,Server
需要不斷重發直至超時,這些僞造的SYN
包將產時間佔用未連接隊列,導致正常的SYN
請求因爲隊列滿而被丟棄,從而引起網絡堵塞甚至系統癱瘓。
三次握手的原因
爲了防止已失效的連接請求報文段突然又傳送到了服務端,因而產生錯誤。
舉例:
Client
發出的第一個連接請求報文段並沒有丟失,而是在某個網絡結點長時間的滯留了,以致延誤到連接釋放以後的某個時間纔到達Server
。本來這是一個早已失效的報文段。但Server
收到此失效的連接請求報文段後,就誤認爲是Client
再次發出的一個新的連接請求。於是就向Client
發出確認報文段,同意建立連接。假設不採用“三次握手”,那麼只要Server
發出確認,新的連接就建立了。由於現在Client
並沒有發出建立連接的請求,因此不會理睬Server
的確認,也不會向Server
發送數據。但Server
卻以爲新的運輸連接已經建立,並一直等待Client
發來數據。這樣,Server
的很多資源就白白浪費掉了。採用“三次握手”的辦法可以防止上述現象發生。例如剛纔那種情況,Client
不會向Server
的確認發出確認。Server
由於收不到確認,就知道Client
並沒有要求建立連接。
四次揮手(斷開TCP連接)
四次揮手(Four-Way Wavehand
)即斷開TCP
連接,指斷開一個TCP
連接時,需要客戶端和服務端總共發送4
個包以確認連接的斷開。整個過程大致如下:
由於TCP
連接時全雙工的,因此,每個方向都必須要單獨進行關閉,這一原則是當一方完成數據發送任務後,發送一個FIN
來終止這一方向的連接,收到一個FIN
只是意味着這一方向上沒有數據流動了,即不會再收到數據了,但是在這個TCP
連接上仍然能夠發送數據,直到這一方向也發送了FIN
。首先進行關閉的一方將執行主動關閉,而另一方則執行被動關閉,上圖描述的即是如此。
- 第一次揮手:
Client
發送一個FIN
,用來關閉Client
到Server
的數據傳送,Client
進入FIN_WAIT_1
狀態。 - 第二次揮手:
Server
收到FIN
後,發送一個ACK
給Client
,確認序號爲收到序號seq+1
(與SYN
相同,一個FIN
佔用一個序號),Server
進入CLOSE_WAIT
狀態。 - 第三次揮手:
Server
發送一個FIN
,用來關閉Server
到Client
的數據傳送,Server
進入LAST_ACK
狀態。 - 第四次揮手:
Client
收到FIN
後,Client
進入TIME_WAIT
狀態,接着發送一個ACK
給Server
,確認序號爲收到序號seq+1
,同時等待2MSL
(報文最大生存時間)進入CLOSED
狀態。Server
收到ACK
後進入CLOSED
狀態,完成四次揮手。
四次揮手的原因
TCP
協議是一種面向連接的、可靠的、基於字節流的傳輸層全雙工模式通信協議,這就意味着,當Client
發出FIN
報文段時,只是表示Client
已經沒有數據要發送了,Client
告訴Server
,它的數據已經全部發送完畢了;但是,這個時候Client
還是可以接受來自Server
的數據;當Server
返回ACK
報文段時,表示它已經知道Client
沒有數據發送了,但是Server
還是可以發送數據到Client
的;當Server
也發送了FIN
報文段時,這個時候才表示Server
也沒有數據要發送了,就會告訴Client
,我也沒有數據要發送了,之後彼此就會愉快的中斷這次TCP
連接。
Client進入TIME_WAIT狀態後等待2MSL(報文最大生存時間)才進入CLOSED狀態的原因
- 保證
TCP
協議的全雙工連接能夠可靠關閉
如果Client
直接CLOSED
了,那麼由於IP
協議的不可靠性或者是其它網絡原因,導致Server沒有收到Client
最後回覆的ACK
。那麼Server
就會在超時之後繼續發送FIN
,此時由於Client
已經CLOSED
了,就找不到與重發的FIN
對應的連接,最後Server
就會收到RST
而不是ACK
,Server
就會以爲是連接錯誤把問題報告給高層。這樣的情況雖然不會造成數據丟失,但是卻導致TCP
協議不符合可靠連接的要求。所以,Client
不是直接進入CLOSED,
而是要保持TIME_WAIT
,當再次收到FIN
的時候,能夠保證對方收到ACK
,最後正確的關閉連接。
- 保證這次連接的重複數據段從網絡中消失
如果Client
直接CLOSED
,然後又再向Server
發起一個新連接,我們不能保證這個新連接與剛關閉的連接的端口號是不同的。也就是說有可能新連接和老連接的端口號是相同的。一般來說不會發生什麼問題,但是還是有特殊情況出現:假設新連接和已經關閉的老連接端口號是一樣的,如果前一次連接的某些數據仍然滯留在網絡中,這些延遲數據在建立新連接之後纔到達Server
,由於新連接和老連接的端口號是一樣的,又因爲TCP
協議判斷不同連接的依據是socket pair
,於是,TCP
協議就認爲那個延遲的數據是屬於新連接的,這樣就和真正的新連接的數據包發生混淆了。所以TCP
連接還要在TIME_WAIT
狀態等待2MSL
,這樣可以保證本次連接的所有數據都從網絡中消失。