TCP報文段格式
TCP雖然是面向字節流的,但TCP傳送帶數據單元確是報文段。TCP報文段分為首部和數據段部分,而TCP的全部功能體現在它在首部中各字段的作用。因此,只有弄清TCP首部各字段的作用才能掌握TCP的工作原理。
TCP報文段首部的前20字節是固定的,后面4N字節是根據需要而增加的選項(N必須是整數)。因此TCP首部的最小長度是20字節。下圖是TCP報文段及首部的格式。
TCP中沒有表示包長度和數據長度的字段。可由IP層獲知TCP的包長由TCP的包長可知數據長度。
- 源端口號
表示發送端端口號,字段長16位(2字節)。
- 目標端口號
表示接收端端口號,字段長16位(2字節)。
- 序列號
字段長32位(4字節)。序列號(有時也叫序號)是指發送數據的位置。每發送一次數據,就會累加一次該數據字節數的大小。
序列號不會從0或1開始,而是在建立連接是由計算機生成的隨機數作為其初始值,通過SYN包傳給接收端主機。然后再將每轉發過去的字節數累加到初始值上表示數據的位置。此外,在建立連接和斷開連接時發送端SYN包和FIN包雖然不攜帶數據,但是也會作為一個字節增加對應的序列號。
- 確認應答號
確認應答號字段長度為32位。是指下一次應該收到的數據的序列號。實際上,它是指已收到確認應答號減一為止的數據。發送端收到這個確認應答以后可以認為在這個序號以前的數據都已被正常接收。例如,主機B接收到了主機A編號為301的數據報,數據報長度為100,于是說明主機B收到了編號為301~400的數據,那么主機B下一次就期望收到編號為401的數據,此時確認應答號就是401。
- 數據偏移
該字段表示TCP所傳輸的數據部分應該從TCP包的哪個位開始計算,當然也可以把它看作TCP首部的長度。該字段長4位,單位為4字節。不包括選項字段的話,上圖所示的TCP的首部為20字節長,因此數據偏移字段可以設置為5,反之,如果該字段的值為5,那說明從TCP包的最一開始到數據部分的開始這20字節為止都是TCP首部,余下的部分為TCP數據。
- 保留
該字段主要是為了以后擴展時使用,其長度為4位(有些教材寫的是6位)。一般設置為0,但即使收到的包在該字段不為0,此包也不會被丟棄。
- 控制位
字段長8位,每一位從左到右分別是CWR、ECE、URG、ACK、PSH、RST、SYN、FIN。當它們對應位上的值為1時,具體含義如下:
CWR:CWR標志與后面的ECE標志都用于IP首部的ECN字段。ECE標志為1時,則通知對方已將擁塞窗口縮小。
ECE:該標志表示ECN-Echo。為1會通知通信對方,從對方到這邊的網絡有擁塞。在收到數據包的IP首部中ECN為1時將TCP首部中的ECE設置為1。
URG:該位為1時,表示包中有需要緊急處理的數據。
ACK:該位為1時,確認應答的字段變為有效。TCP規定除了最初建立連接時的SYN包之外該位必須設置為1。
PSH:該位為1時,表示需要將收到的數據立刻傳給上層協議。為0時,則不需要立即傳而是先進行緩存。
RST:該位為1時表示TCP連接中出現異常必須強制斷開連接。
SYN:用于建立連接。SYN為1表示希望建立連接,并在其序列號的字段進行序列化初始值的設定。
FIN:該位為1時,表示今后不會再有數據發送,希望斷開連接。當通信結束希望斷開連接時,通信雙方的主機之間就可以相互交換FIN位置為1的TCP段。每個主機又對對方的FIN包進行確認應答以后就可以斷開連接。
- 窗口大小
該字段長為16位。用于通知從相同TCP首部的確認應答號所指位置開始能夠接收端數據大小(8位字節)。TCP不允許發送超過此處所示大小的數據。不過,如果窗口為0,則表示可以發送窗口探測,以了解最新的窗口大小。但是這個數據必須是1個字節。
- 校驗和
TCP的校驗和與UDP相似,區別在于TCP的校驗和無法關閉。
TCP和UDP一樣在計算校驗和的時候使用TCP首部。接收端在收到TCP數據段以后,從IP首部獲取IP地址信息構造TCP偽首部,再進行校驗和計算。由于校驗和字段里保存著除本字段以外其他部分的和的補碼值,因此如果計算機校驗和字段在內的所有數據的16位和以后,得出的結果是“16位全部為1”說明所收到的數據是正確的。
- 緊急指針
該字段長16位。只有在URG控制位為1時有效。該字段的數值表示本報文段中緊急數據的指針。從數據部分的首位到緊急指針所指示的位置為止為緊急數據,因此也可以說緊急指針指出了緊急數據的末尾在報文段中的位置。
例如,在Web瀏覽器中點擊停止按鈕,或使用telnet
輸入ctrl+c
時都會有URG為1的包。此外,緊急指針也用作表示數據流分段的標志。
- 選項
選項字段用于提高TCP的傳輸性能。因為根據數據偏移(首部長度)進行控制,所以其長度最大為40字節。另外,選項字段盡量調整為32位的整數倍。
三次握手
我們都知道TCP是面向連接和可靠傳輸的傳輸協議,為了準確無誤的將數據送往目標處,TCP協議采用的三次握手策略。用TCP協議把數據包送出去后,TCP不會對傳送后的請求置之不理,它會向對方確認是否成功到達。握手過程中使用了TCP的標志——SYN和ACK。
什么是面向連接?
連接:在一個連接中傳輸的數據是有關系狀態的,比如需要確定傳輸的對端正處在等待發送或接收到狀態上。需要維護傳輸數據的關系,比如數據流的順序。典型的例子就是打電話。
無連接:不用關心對端是否在線。每一個數據段的發送都是獨立的一個數據個體,數據和數據之間沒有關系,無需維護之間的關系。典型的例子就是發短信。
什么是可靠?
是指數據在傳輸過程中不會被損壞或丟失,保證數據可以正確到達。相反就是不可靠。
講解三次握手之前,需要先了解一些專業名詞。
- seq:對應首部中的序列號字段,指明希望收到的下一個數據的序號是什么。
- ack:對應首部中的確認號字段,表明多少之前的數據都已經收到了。
- LISTEN:服務端上有服務就會監聽一個窗口,等待客戶端來連接它。
- SYN_SENT:客戶端想要連接服務器,會先打招呼,這是三次握手當中的第一次,它把請求發出去后,就變成了這個狀態,表示等待服務端的確認。
- SYN_RECEIVED:服務器接收到了客戶端的請求,之后需要反饋給客戶端確認信息,并且同時要把自己的請求信息一起發送給客戶端,此時就會變成SYN_RECEIVED狀態。
- ESTABLISHD:經過三次握手后,客戶端和服務器端都會變成ESTABLISHD狀態,表示雙方建立了連接。只有雙方都是該狀態,才可以順利的傳輸數據。
- CLOSED:徹底關閉連接的狀態(這是為方便描述假想的狀態,實際不存在)
了解完TCP的特點后,再來看看三次握手:
一開始客戶端和服務端都是關閉狀態,相當于它們連個互相不認識,為了更好的描述三次握手,我把客戶端叫做小客,把服務端叫做小服。
當小客需要小服幫忙的時候,需要先給小服發消息:“在嗎?小服,我需要你幫我一個忙。”(客戶度會發送SYN=1,seq=x,x是一個隨機數)
小服收到消息后,會給小客發送消息:“你好小客,可以幫你,需要我怎么幫你呢?”(服務端發送SYN=1,ACK=1,同時發送seq=y,ack=x+1,這里的ack為客戶端發送端seq數值加1)
小客收到小服的反饋后,給小服說了一句:“謝謝你小服,我需要你幫我……”(客戶端再次跟服務端發送ACK=1,seq=x+1,ack=y+1)。
小服再次收到小客的反饋后,小服就開始幫小服做事情了。
三次握手原理總結:
- 客戶端發送建立TCP連接的請求報文,其中報文中含有seq序列號,由發送端隨機生成,并且將報文中的SYN字段設置為1,表示需要建立TCP連接。(SYN=1,seq=x,x為隨機數)
- 服務端回復客戶端發送的TCP連接請求報文,其中包含seq序列號,是由回復端隨機生成的,并且將SYN設置為1ACK也設置為1,表明同意連接,并且產生ack字段,ack字段數值是在客戶端發送過來的序列號seq的基礎上加1進行回復,以便客戶端收到消息時,知道自己的TCP建立請求已得到驗證。(SYN=1,ack=x+1,seq=y,y為隨機值)這里的ack+1可以理解為是確認和誰建立連接。
- 客戶端收到服務端發送的TCP建立驗證請求后,會使自己的序列號加1表示,并且再次回復ACK驗證請求,此時SYN=0,因為這既不是請求連接,也不是同意連接,在服務端發過來的seq上加1進行回復。(SYN=1,ack=y+1,seq=x+1)
三次握手的原因
為什么要三次握手,不能兩次、四次或者其它次數嗎?這是面試中經常會被提到的問題。
相信有些人的回答是:”因為三次握手才能保證雙方具有接收和發送的能力。”
這種回答是沒有問題的,但這回答是片面的,并沒有說出主要的原因。相信面試官也不想聽這種回答。
在前面我們知道了什么是TCP連接:
- 用于保證可靠性和流量控制維護某些狀態的信息,這些信息的組合,包括Socket、序列號和窗口大小稱為連接。
所以,回答方向的重點就是為什么三次握手才可以初始化Socket、序列號和窗口大小并建立TCP連接
接下來,以三個方面分析三次握手的原因: - 三次握手才可以阻止重復歷史連接的初始化(主要原因)
- 三次握手才可以同步雙方的初始化序列號
- 三次握手才可以避免資源浪費
原因一:避免歷史連接
簡單來說,三次握手的首要原因是為了防止舊的重復連接初始化造成混亂。
我們考慮一個場景,客戶端先發送了SYN(seq = 90)報文,然后客戶端宕機了,而且這個SYN報文還被網絡阻塞了,服務端并沒有收到,接著客戶端重啟后,又重新向服務端建立連接,發送了SYN(seq = 100)報文(注意!不是重傳SYN,重傳的SYN的序列號是一樣的)。
看看三次握手是如何阻止歷史連接的:
客戶端連續發送多次SYN(都是同一個四元組)建立連接的報文,在網絡擁堵情況下:
- 一個
舊的SYN報文
比最新的SYN
報文早到達了服務端,那么此時服務端就會回一個SYN + ACK
報文給客戶端,此報文中的確認號是91(90+1)。 - 客戶端收到后,發現自己期望收到的確認號應該是100+1,而不是90+1,于是就會回RST報文。
- 服務端收到RST報文后,就會釋放連接。
- 后續最新的SYN抵達了服務端后,客戶端與服務端就可以正常的完成三次握手了。
上述中的舊SYN報文稱為歷史連接,TCP使用三次握手建立連接的最主要原因就是防止歷史連接初始化了連接。
如果是兩次握手連接,就無法阻止歷史連接,那為什么 TCP 兩次握手為什么無法阻止歷史連接呢?
主要是因為在兩次握手的情況下,服務端沒有中間狀態給客戶端來阻止歷史連接,導致服務端可能建立一個歷史連接,造成資源浪費。
可以看到,如果采用兩次握手連接建立TCP連接的場景下,服務端在向客戶度發送數據前,并沒有阻止掉歷史連接,導致服務端連接建立了一個歷史連接,又白白發送了數據,妥妥浪費了服務端的資源。
因此,要解決這種現象,最好就是在服務端發送數據前,也就是建立連接之前,要阻止掉歷史連接,這樣就不會造成資源浪費,而要實現這個功能,就需要三次握手。
原因二:同步雙方初始序列號
TCP協議的通信雙方,都必須維護一個序列號,序列號是可靠傳輸的一個關鍵因素,它的作用是:
- 接收方可以去除重復的數據。
- 接收方可以根據數據包的序列號按序接收。
- 可以標識發送出去的數據包中,哪些是已經被對方收到的(通過ACK報文中的序列號知道)。
可見,序列號在TCP連接中占據著非常重要的作用,所以當客戶端發送攜帶初始序列號的SYN報文段時候,需要服務端返回一個ACK應答報文,表示客戶端的SYN報文已經被服務端成功接收,那當服務端發送初始序列號給客戶端的時候,依然也要得到客戶端的應答回應,這樣一來一回,才能確保雙方的初始序列號能被可靠的同步。
四次握手其實也能夠可靠的同步雙方的初始化序列號,但由于第二步和第三步可以優化成一步,所以就成了三次握手。
而兩次握手只保證了一方的初始化序列號能被對方成功接收,沒辦法保證雙方的初始化序列號都能被確認接收。
原因三:避免資源浪費
如果只有兩次握手,當客戶端發送的SYN報文在網絡中阻塞,客戶端沒有接收到ACK報文,就會重新發送SYN,由于沒有第三次握手,服務端不清楚客戶端是否收到了自己回復的ACK報文,所以服務端每收到一個SYN就只能先主動建立一個連接,這會造成什么情況呢?
如果客戶端發送的SYN報文在網絡中阻塞了,重復發送多次SYN報文,那么服務端在收到請求后就會建立多個冗余的無效鏈接,造成不必要的資源浪費。
小結:
TCP建立連接時,通過三次握手能防止歷史連接的建立,能減少雙方不必要的資源開銷,能幫助雙方同步初始化序列號。序列號能夠保證數據包不重復、不丟棄和按序傳輸。
不使用兩次握手和四次握手的原因:
- 兩次握手:無法阻止歷史連接的建立、會造成雙方資源的浪費、也無法可靠的同步雙方序列號;
- 四次握手:三次握手就已經理論上最少可靠連接建立,所以不需要使用更多的通信次數。
四次揮手
還是需要先了解一些專業名詞
- FIN_WAIT_1:客戶端和服務端傳輸完數據后,總有一方需要需要主動發起關閉連接的請求,這個FIN_WAIT_1狀態出現在主動關閉的一方。當它發起關閉連接的請求后,就會變成此狀態。它需要等待對方的確認。
- FIN_WAIT_2:主動關閉方接收到被動關閉方的確認消息后,就會變成此狀態。
- CLOSE_WAIT:被動關閉方收到主動方的關閉請求后,會發出確認消息,確認消息發出后就出現了CLOSE_WAIT。
- LAST_ACK:被動關閉方除了發送確認信息外,還需要發送關閉確認信息給對方,這個信息發送完畢后,就會成為LAST_ACK狀態,此時被動關閉方只需要等待主動關閉方的一個回饋確認信息。
- TIME_WAIT:主動關閉方將最后一次的確認信息發送給被動關閉方,就會處于TIME_WAIT狀態,該狀態只出現在主動關閉方,它只需等待一個時間就會徹底關閉,這個等待的時間為2*MSL(Maximum Segment Lifetime,報文最長存活時間)。因為它需要一個等待時間,所以在Linux系統里,這個狀態是最常見、最多的狀態。
- CLOSING: 幾乎看不到的狀態,表示正在關閉連接,這個是瞬時完成的。
小客在小服的幫助下得到了圓滿的結束,小客想對小服提出感謝。
小客對小服說:“謝謝你小服,感謝你的幫助,要是沒有你,真不知道該怎么辦。”(FIN=1 seq=u)
小服對小客說:“不用客氣,這陣子確實有點累了,需要休息一下,以后要是有需要幫忙的地方,再叫我就是了。”(ACK=1 seq=v ack=u+1)
小服再對小客說:“要是沒有其它事情的話,我就先去休息了。”(FIN=1 seq=w ack=u+1)
小客回復小服:“好的,再次感謝你,拜拜。”(ACK=1 seq=u+1 ack=w+1)
四次揮手原理總結:
- 客戶端發送斷開TCP連接請求的報文,其中報文中包含seq序列號,是由發送端隨機生成的,并且還將報文中的FIN字段設置為1,表示需要斷開TCP連接。
- 服務端會回復客戶端發送的TCP斷開請求報文,其中包含seq序列號,是由回復端隨機生成的,而且會產生ACK字段,ACK字段數值是在客戶端發過來的seq序列號基礎上加1進行回復,以便客戶端收到消息時,知曉自己的TCP斷開請求已經得到驗證。
- 服務端在回復完客戶端的TCP斷開請求后,不會馬上進行TCP連接的斷開,服務端會先確保斷開前,所有傳輸到客戶端的數據是否已經傳輸完畢,一旦確認傳輸完成,就會回復報文的FIN設置為1,并且產生隨機seq序列號。
- 客戶端收到服務端的TCP斷開請求后,會回復服務端的斷開請求,包含隨機生成的seq字段和ACK字段,ACK字段會在服務端的TCP斷開請求的seq基礎上加1,從而完成服務端請求的驗證回復。
為什么客戶端在TIME_WAIT階段要等待2MSL?
一是為了保證客戶端發送的最后一個ACK報文段能夠到達服務端,確保服務端能正常進入CLOSED狀態。服務端在1MSL內沒有收到客戶端發出的ACK確認報文,就會再次向客戶端發出FIN報文。
二是為了避免新舊連接混淆。由于網絡滯留,客戶端可能發送了多次請求連接的請求,經過時間2MSL,就可以使本連接持續時間內所產生的所有報文段都從網絡中消失,這樣就可以使下一個新的連接中不會出現這種舊的連接請求報文段。