TCP 標頭格式
TCP 頭的格式如圖:
可參看:TCP協定以及其報頭結構分析 、TCP 標頭詳解
- 16位端口号:告知主機該封包段是來自哪裡(源端口)以及傳給哪個上層協定或應用程式(目的端口)。
- 32位序号:一次TCP通信過程中的一個傳輸方向上的位元組流的每個位元組的編号。
- 32位确認号:用作對另一個發送來的TCP封包段的響應。其值是收到的TCP封包段的序号值加1.假設主機A和主機B進行TCP通信,那麼A發送出的TCP封包段不僅攜帶自己的序号,而且包含對主機B發送來的TCP封包段的确認号。反之,B發出的TCP封包段也同時攜帶自己的序号和對A發送來的封包段的确認号。
- 4位首部長度:辨別該TCP頭部有多少個32bit(4位元組),四位最大表示15,是以TCP報頭最長是60位元組。
- 6位标志位 :
- URG:表示緊急指針是否有效。
- ACK:表示确認号是否有效。我們稱攜帶ACK标志的TCP封包段為确認封包段。
- PSH:提示接收端應用程式應該立即從TCP接受緩沖區中讀走資料,為接受後續資料騰出空間,如果不将接收到的資料讀走,它們就會一直停留在TCP封包段。
- RST:表示要求對方重建立立連接配接。我們稱攜帶RST标志的TCP封包段為複位封包段。
- SYN:表示請求建立一個新連接配接。稱攜帶SYN标志位的TCP封包段為同步封包段。
- FIN:表示通知對方本端要關閉連接配接了。稱攜帶FIN标志的TCP封包段為結束封包段。
- 16位視窗大小:這是TCP流量控制的一個手段。這裡的視窗指的是接受通告的視窗。告訴對方本端的TCP接收緩沖區還能容納多少位元組的資料,這樣就對方就可以控制發送資料的速度。
- 16位校驗和:由發送端填充,接收端對TCP封包端執行CRC算法以檢驗TCP封包段在傳輸過程中是否損壞(檢驗部分包括報頭和資料部分)。
- 16位緊急指針:為一個正的偏移量。它的序号字段的值相加表示最後一個緊急資料的下一個位元組的序号。即緊急指針相對于目前序号的偏移。
- 剩下40位元組為選項字段。
序号解決包的亂序問題。
确認序号确認對方收到,沒有收到則重新發送。
狀态位維護連接配接的狀态。SYN發起連接配接,ACK回複,RST重新連接配接,FIN結束連接配接。
視窗大小用于流量控制。
通過對 TCP 頭的解析,要掌握 TCP 協定,重點應該關注以下幾個問題:
- 順序問題 ,穩重不亂;
- 丢包問題,承諾靠譜;
- 連接配接維護,有始有終;
- 流量控制,把握分寸;
- 擁塞控制,知進知退。
TCP 的三次握手
TCP 的連接配接建立,我們常常稱為三次握手。
A:您好,我是 A。
B:您好 A,我是 B。
A:您好 B。
B收到A發來的 您好 B。三次握手完成
常稱為“請求 -> 應答 -> 應答之應答”的三個回合。
什麼要三次,而不是兩次?為了可靠,為什麼不是四次?
兩次握手:存在A的請求連接配接B的封包在網絡中滞留,延誤到連接配接釋放後才到達B,B以為是A新發起的連接配接請求,同意建立連接配接,浪費了資源:端口、記憶體等。
三次握手可防止上述情況發生,因為A不确認,B就不會建立連接配接。四次握手是可以的,四十次都可以,關鍵四百次也不能保證就真的可靠了,三次握手隻要雙方的消息都有去有回,就基本就認為連接配接可以了。
大部分情況下,A和B建立連接配接後,A會馬上發資料。如果不發,B可以開啟keepalive,發送探活包。B也可以對于長時間不發包的用戶端,主動關閉。
三次握手除了雙方建立連接配接外,主要還是為了溝通一件事情,就是TCP 包的序号的問題。
A 要告訴 B,我這面發起的包的序号起始是從哪個号開始的,B 同樣也要告訴 A,B 發起的包的序号起始是從哪個号開始的。為什麼序号不能都從 1開始呢?因為這樣往往會出現沖突。
TCP三向交握狀态機
1、用戶端和服務端都處于 CLOSED 狀态。
用戶端: CLOSED ·服務端: CLOSED
2、服務端主動監聽某個端口
用戶端: CLOSED ·服務端: LISTEN
3、用戶端主動發起連接配接 SYN
用戶端: SYN-SENT ·服務端: LISTEN
4、服務端收到發起的連接配接,傳回 SYN,并且 ACK 用戶端的 SYN
用戶端: SYN-SENT ·服務端: SYN-RCVD
5、用戶端收到服務端發送的 SYN 和 ACK 之後,發送 ACK 的 ACK
用戶端: ESTABLISHED ·服務端: SYN-RCVD
6、服務端收到 ACK 的 ACK 之後
用戶端: ESTABLISHED ·服務端: ESTABLISHED
此時用戶端服務端都是一發一收了
TCP 四次揮手
好說好散,這常被稱為四次揮手。
A:B 啊,我不想玩了。
B:哦,你不想玩了啊,我知道了。
B:A 啊,好吧,我也不玩了,拜拜。
A:好的,拜拜。
整個連接配接就關閉了,上面是和平分手的場面,但也會有異常
異常1:A 說完“不玩了”之後,直接跑路,會導緻問題,因為 B 還沒有發起結束,而如果 A 跑路,B 就算發起結束,也得不到回答,B 就不知道該怎麼辦了。
異常2:A 說完“不玩了”,B 直接跑路,也是有問題的,A 不知道 B 是還有事情要處理,還是過一會兒會發送結束。
為了解決這些問題,TCP 協定專門設計了幾個狀态來處理這些問題。如下圖狀态時序圖。
TCP四次揮手狀态機
1、初始狀态A、B均處于 ESTABLISHED 狀态。
A: ESTABLISHED B: ESTABLISHED
2、A 說“不玩了”
A: FIN_WAIT_1 B: ESTABLISHED
3、B 收到“A 不玩”的消息後,發送知道了到A
A: FIN_WAIT_1 B: CLOSE_WAIT
4、A 收到“B 說知道了”
A: FIN_WAIT_1 B: CLOSE_WAIT
如果這個時候 B 直接跑路,則 A 将永遠在這個狀态。TCP 協定裡面并沒有對這個狀态的處理,但是 Linux 有,可以調整 tcp_fin_timeout 這個參數,設定一個逾時時間。
5、如果 B 沒有跑路,發送了“B 也不玩了”的請求到達 A
A: FIN_WAIT_2 B: LAST-ACK
6、A 發送“知道 B 也不玩了”的 ACK
A: TIME-WAIT(等待2MSL) B: LAST-ACK
按說 A 這時可以跑路了,但是直接跑路有問題:
問題1 :最後的這個 ACK 萬一 B 收不到呢?
則 B 會重新發一個“B 不玩了”,這個時候 A 已經跑路了的話,B 就再也收不到 ACK 了,因而 TCP 協定要求 A最後等待一段時間 TIME_WAIT,這個時間要足夠長,長到如果 B 沒收到 ACK 的話,“B 說不玩了”會重發的,A 會重新發一個 ACK 并且足夠時間到達 B。
問題2 :A 直接跑路,A 的端口就直接空出來了,但是 B 不知道,B 原來發過的很多包很可能還在路上,如果 A 的端口被一個新的應用占用了,這個新的應用會收到上個連接配接中 B 發過來的包,雖然序列号是重新生成的,但是這裡要上一個雙保險,防止産生混亂,因而也需要等足夠長的時間,等到原來 B 發送的所有的包都死翹翹,再空出端口來。
7、B 收到 A 發送“知道 B 也不玩了”的 ACK,并且 A 也等夠 2MSL
A: CLOSED B: CLOSED
MSL是Maximum Segment Lifetime,封包最大生存時間它是任何封包在網絡上存在的最長時間,超過這個時間封包将被丢棄。
還有一個異常情況:B 超過了 2MSL 的時間,依然沒有收到它發的 FIN 的 ACK,怎麼辦呢?
按照 TCP 的原理,B 當然還會重發 FIN,這個時候A 再收到這個包之後,A 就表示,我已經在這裡等了這麼長時間了,已經仁至義盡了,之後的我就都不認了,于是就直接發送 RST,B 就知道 A 早就跑
TCP 狀态機
将連接配接建立和連接配接斷開的兩個時序狀态圖綜合起來,就是這個著名的 TCP 的狀态機
加黑加粗的部分,是上面說到的主要流程,其中阿拉伯數字的序号,是連接配接過程中的順序,而大寫中文數字的序号,是連接配接斷開過程中的順序。加粗的實線是用戶端 A 的狀态變遷,加粗的虛線是服務端 B 的狀态變遷。
小結
TCP 標頭很複雜,主要關注五個問題,順序問題,丢包問題,連接配接維護,流量控制,擁塞控制;
連接配接的建立是經過三次握手,斷開的時候四次揮手,狀态圖
參考資料:
趣談網絡協定(極客時間)連結:
http://gk.link/a/106nW
TCP協定以及其報頭結構分析:
https://blog.csdn.net/MBuger/article/details/74078777
TCP 標頭詳解:
https://blog.51cto.com/13854765/2163296
GitHub連結:
https://github.com/lichangke/LeetCode
知乎個人首頁:
https://www.zhihu.com/people/lichangke/
CSDN首頁:
https://me.csdn.net/leacock1991
歡迎大家來一起交流學習