1. 程式人生 > >TCP協議學習總結(上)

TCP協議學習總結(上)

在計算機領域,資料的本質無非0和1,創造0和1的固然偉大,但真正百花齊放的還是基於0和1之上的各種層次之間的組合(資料結構)所帶給我們人類各種各樣的可能性。例如TCP協議,我們的生活無不無時無刻的站在TCP協議這個“巨人”的肩膀上,最簡單的一個開啟手機的動作。所以對TCP的認識和理解,可謂越來越常識化。

TCP/IP五層協議

 雖然TCP是一種計算機網路協議,但本質還是人與人之間的一種約定,只不過由計算機去執行而已,把協議的細節與作用解耦,讓我們人類只需專注於基於它的應用呈現之上即可。協議即“規則”,如果我們把光纖“橫斜面”剖析,我們看到的就是資料的本質0和1,如下圖所示:

0和1是點對點之間通訊的資訊“載體”,我們需要有一個規則去翻譯這些“載體”,好比如小白和小黑之間的“敲聲傳話遊戲”的約定,他們可以約定“敲一下”代表“是”,“敲兩下”代表“不是”等。這些“敲聲”跟光纖上的“0”和“1”都是承載著一樣的任務——資訊載體。

 從整個網路層次來看,TCP/IP協議體系是網路的一個核心協議組,有一點需要知道的是TCP/IP協議體系並非只有TCP協議和IP協議,而是包含了物理層、鏈路層、網路層、運輸層、應用層,而每一層次又有不同的協議,例如運輸層協議除了TCP協議還有UDP協議。當然這裡我只是為了接下來學習TCP協議的一個巨集觀認識。從上圖可以看出,從0和1的基本資訊單元到TCP協議的資料結構還要經過鏈路層和網路層的層層分解,換句話說,也就是TCP協議的資料以“段”單元,封裝在網路層的IP協議上,IP協議的資料是以“資料報”為單元,它同樣封裝在鏈路層的乙太網標準協議裡面。本文的重點在TCP協議的學習,瞭解了TCP的原理,其他協議的資料結構和邏輯大同小異了。

 TCP的首部

 

從“TCP/IP五層協議體系圖”可以看出,每一個協議都會有個“頭部”,TCP也不例外,其實這個“頭部”就是該協議的資料結構以及規則的說明,但無論協議的玩法如何變化,它還是離不開0和1的資訊載體。

源埠號:我們都知道IP是跟主機相關,而每臺主機又可以有不同的應用程序在執行,所以埠更多可以指執行在主機上的應用程序,所以源埠號也就是基於TCP協議傳輸資料的“傳送方”。

目的埠:就是等待TCP協議傳送方資料的“接收方”,其實所謂的埠也就是應用程序與應用程序之間通訊的監聽出入口。

序列號:這個數字是用來表示通訊雙方“單向”資料量流動數量表示,上面所介紹的0和1是最小的資料傳輸單元,我們稱為“位元(bit)”。而這個序列號記錄的是以“位元組”為單位的計數器(1位元組=8位元)。例如A要傳輸給B的512位元組資料,假設初始序列號為1024(注意:每次初始化序號都會不一樣,TCP有一個比較複雜的初始化演算法),那麼他們傳輸過程的序列號為1536。這個序列號會隨著雙方“交流”而不斷的增加,因為序列號一共32位元,所以最大值也就是2^32-1,到達最大值後重新從0開始。因為TCP是一個可靠的協議,序列號的存在是其可靠的關鍵因素之一。

 

確認序列號:既然每個傳輸的位元組都被計數,確認序列號包含傳送確認的一端所期望收到下一個序號。因此,確認序列號應當是上次已成功接收到資料位元組序列號加1。只有ACK標識(下面會介紹)為1時確認序列號才生效。因為TCP為應用層提供雙工服務,意味著資料能在兩個方向上獨立地進行傳輸,因此連線的每一端(客戶端和服務端)必須保持每個方向上的傳輸序列號。例如A傳送給B的序列號為1024(A維護),但B傳送給A的有自己的序列號需要維護(B維護)。

首部長度:TCP首部的“選項”不啟用,那麼TCP的頭部就是20位元組,但因為存在“選項”的部分,所以頭部可能存在大於20位元組的可能性。因為“首部長度標識”有4位,所以最大值為2^4-1=15,而這個標識維護頭部的長度是以32位元為單元,所以頭部最大長度為15*32位元(4位元組)=60位元組。

標誌:每個標誌佔1位元,它們中的多個可同時被設定為1,每個標誌的用法如下:

URG:緊急指標(urgent pointer)有效;

ACK:確認序號有效;

PSH:接收方應該儘快將這個報文段交給應用層;

RST:重建連線;

SYN:同步序號用來發起一個連線;

FIN:傳送端完成傳送任務;

視窗大小:TCP的流量控制由連線的每一端通過宣告的視窗大小來提供(以位元組為單位),視窗大小是一個16位元欄位,因而視窗最大為65535位元組。換個說法,視窗好比如“緩衝區”,TCP是一個雙工單向傳送的通訊協議,雙方都需要有自己的視窗(緩衝區)大小相互告知,如果接收到的應用處理速度慢(從緩衝區消費資料慢),那麼它的視窗很容易就滿了,傳送方就會停止傳送,等到接受方的視窗有“空餘”了才繼續傳送。

檢驗和:檢驗和(類資料簽名)覆蓋了整個的TCP報文段:TCP首部和TCP資料,因為TCP是一個可靠的協議,所以這是強制性的欄位,由傳送方計算和設定,並由接收方進行驗證,這就是可靠性保證的重要手段。

緊急指標:只有當URG標誌置1時緊急指標才有效。緊急指標是一個正的偏移量,和序號欄位中的值相加表示緊急資料最後一個報文段。

選項:就是TCP頭部的不是“必須”的選項,例如常見的可選欄位是“最長報文大小”,又稱為MSS(Maximun Segment Size),每個連線方通常都在通訊的第一個報文段中指明這個選項。

資料:整個TCP報文段是又報文頭部和報文資料組成的,除去了頭部就是資料,但資料是可空的,例如建立連線(SYN)和結束傳輸(FIN)的TCP報文都是沒有資料的。

 TCP連線的建立和終止

 

TCP建立連線需要三次握手,分別如下:

1)、客戶端(請求方)傳送一個SYN段指明客戶打算連線的伺服器埠,以及把初始化序號x附上,這就是大名鼎鼎的SYN報文段,在介紹頭部的時候已經提過,SYN報文段是沒有資料的,因為連線都沒正式連線,傳送資料沒意義。但也提到了客戶端會附上它的最大報文段,也就是告訴接收方它最大的一個報文段能接受多少資料。

2)、服務端(處於監聽狀態)收到SYN請求後發回包含服務端的初始序號的SYN報文段作為應答(上文提到過客戶端和服務端的初始序號都是各自維護的)。同時,將確認序號設定為客戶的ISN加1(因為SYN將佔用一個序號),以對客戶的SYN報文段進行確認。在服務端想客戶端響應SYN的時候同樣可能會附上它接收的最大報文段,但記住,畢竟最大報文段是可選的,不一定會存在,不相互告知的話就會使用預設值。

3)、客戶端必須將確認序號設定為伺服器的ISN加1一對伺服器的SYN報文段進行確認。

當以上三個報文段完成互動後就證明連線已經建立,這個過程也成為“三次握手”。接下來客戶端就可以傳送資料給服務端,服務端可以響應資料。其實很多時候,客戶端在第三個報文段(也就是第三次握手)的時候就已經附帶資料了。因為它已經不需要等待對方第四次握手的互動確認。正常連線的第四個報文段也是客戶端傳送資料的報文段,所以既然第三次和第四次都是客戶端,為了省了一個互動,客戶端可以直接從第三個報文段(應答服務端ack)附上資料。

建立一個連線需要三次握手,而終止一個連線需要經過4次握手,這是由於TCP的半關閉(half close)造成的。既然一個TCP連線是全雙工的(即資料在兩個方向上能同時傳遞),因此每個方向必須單獨地進行關閉。當一端收到一個FIN,它必須通知應用層另一端已經終止了那個方向的資料傳送。傳送FIN通常是應用層進行關閉的結果。比較常見的還是客戶端關閉,但服務端也可以設定主動關閉,例如Nginx相關策略配置。

TCP終止連線需要四次握手,分別如下:

1)、首先關閉的一方(即傳送第一個FIN)將執行主動關閉,上圖顯示主動關閉的一方是客戶端。

2)、當服務端收到這個FIN報文段時,它將發回一個ACK,確認序號為收到的序號加1,就像上圖的ack=u+1,因為FIN跟SYN一樣也佔用一個序號。

3)、服務端把收到的FIN的訊息告訴應用程式(傳送一個檔案結束符),接著這個應用程式就會關閉它的連線(以上提過,建立和關閉都是由應用主動發起的),導致服務端的TCP端傳送一個FIN給客戶端。需要注意的是,畢竟TCP是雙工的,客戶端關閉連線不代表服務端就可以立刻關閉,如果客戶端發起關閉的時候,服務端還沒有響應完資料給客戶端,服務端還是需要把資料發完了再去關閉的,而客戶端主動發起了閉關也不會立刻罷工,它還是會進入“FIN_WAIT2”狀態進行資料接收,直到服務端傳送完了並最後傳送結束連線報文段(FIN),才進入TIME_WAIT狀態。

4)、客戶端收到服務端的FIN報文段時,它會立刻對此FIN進行ACK回覆,服務端收到後就直接進入關閉狀態(CLOSED)。

因為TCP是全雙工的,雙方都各種維護自己單向傳送資料的連線,所以必然會存在雙方同時主動關閉的情況,如下圖所示:

當雙方同時向對方傳送FIN執行主動連線時,雙方均從ESTABLISHED狀態變為FIN_WAIT_1狀態。雙方都收到FIN後,狀態由FIN_WAIT_1變遷至CLOSING,併發送最後的ACK。當收到ACK時,雙方的狀態變為TIME_WAIT。

TCP的狀態遷變

 通過以上建立和終止連線可以看到,無論客戶端還是服務端,無論是連線方還是結束方都存在許多“狀態”,每個狀態隨著各種條件不斷變化,具體狀態的遷變可以通過下圖來進行總結。

2MSL等待狀態

從上圖遷變狀態可以看到,TCP主動關閉的一方都會進入TIME_WAIT狀態,也稱為2MSL(最大報文段生存時間)等待狀態。之所以要等待,是因為關閉方要確認處於“CLOSE_WAIT”狀態的被關閉方收到它最後的ACK報文,報文的在網路上單向傳送的最大時間叫做MSL,那麼等待確認報文來回的時間就是2MSL,如果被關閉方在2MSL內都沒有收到ACK,它會繼續傳送FIN報文,而如果關閉方在2MSL內沒有收到對方的報文就預設對方已經收到。

報文在網路上的生存時間並不只有TCP決定的,在網路層的IP協議對資料報同樣存在著網路單向傳送的時間限制,這個限制的約定叫TTL(Time To Live)。TTL的時間單位並非時間單位,而是“跳數”,資料包每經過一個路由就叫“一跳”,不同系統對IP資料包的跳數初始值都不一樣,例如有些Linux預設值是255。每經過一個路由,總生命跳數就減1,直到為0都還沒有到達目的地就丟棄。255跳到底是多少秒呢?其實這都是一個不確定數字。如果一個數據包經過255個路由都還沒到達目的地,我想目的地可能是“火星”。並TCP是“坐”在IP協議之上的,所以TCP的MSL肯定不能比TTL短,RFC793[Postel 1981c]指出MSL為2分鐘。然而,實現中的常用值是30秒,1分鐘或2分鐘。要知道,0和1在光纖上傳送的速度是“光速(約300000km/s)”,30秒的時間跑了不知道多少趟地球了,所以正常情況下都會大於TTL了(除非部分路由十分磨蹭)。如果做過一些高併發系統的同學,多少會遇到一些諸如time_wait過多的現象,例如WEB伺服器配置主動關閉連線策略或連線有效時間短而主動關閉,大量的time_wait會佔用檔案描述符,而很容易導致耗光系統預設的1024個最大檔案開啟數(fs.file-max)而無法正常服務。

同時開啟和同時關閉

 有時候TCP建立連線不一定必須是三次握手,有時可能會是4次。沒錯,當雙發同時進行請求主動開啟連線的時候就是4次,如下圖所示。這個時候,並沒有誰是客戶端誰是服務端之稱,因為雙方都有主動傳送資料的權利。這種情況應該很少見,如果需要模擬還是可以的,把雙方的網速通過某些手段把它降低,那麼就有可能演示。

學習總結

 本次總結更多是對TCP協議的一個基礎瞭解,包括TCP建立連線的正常三次握手和十分罕見的同步建立連線的4次握手,以及關閉連線的正常4次握手和同步關閉連線導致雙方都進入TIME_WAIT狀態的4次握手。最後總體學習了TCP客戶端以及服務端各種狀態遷變的概要圖,十分清晰地對TCP各種概況的描述,以及為什麼會有TIME_WAIT和2MSL的概念。