自上而下的理解網路(4)——TCP篇

語言: CN / TW / HK

自上而下的理解網路(4)——TCP篇

本系列文章的主題是自上而下的理解網路,這裡的之上而下,只要指的是基於HTTP的網路服務。我們只要從上之下的將這一過程理解透徹,對於其他的應用來說,只是協議不同,原理是相似的。通過本系列前面幾篇部落格的介紹,我們瞭解了在瀏覽器中輸入一個域名或App通過一個域名訪問後端服務介面時,域名會轉換成IP地址,其實只有IP地址還不夠,理論上還需要一個埠號用來確認服務主機上對應的應用程式。只是在實際的應用中,HTTP協議預設的埠號為80,HTTPS協議預設的埠號為443。

HTTP提供了應用層的資料定義結構(HTTPS又加入了安全性的保障),應用層的協議規範了不同裝置,網路環境下的服務端與客戶端的應用互動格式。現在,我們需要關心下這些應用資料是如何在兩端間進行傳輸了。不知你是否還記得,我們之前有提到過網路的分層,下圖描述了在TCP/IP協議簇中各個協議所屬的網路層級:

可以看到,當應用層將應用資料組裝好後,並不關心資料的傳輸,傳輸層來負責將資料傳輸到目標主機。HTTP是基於TCP的一種應用層協議,關於UDP,其與TCP還是有很大的差別,我們本篇部落格暫不予討論。

一.先空談些理論

關於TCP,大部分開發者或計算機專業的同學都不陌生,但是可能也僅僅處於只是不陌生的階段,對其工作原理,協議內容都不甚明瞭。在分層網路模型中,每一層的協議都會再上一層資料的基礎上增部分頭資訊,學習協議,其實就是學習這些頭資訊的意義和用法。

我最早了解到TCP是在學校的網路相關課程中,後來在工作中,重學計算機相關知識中每次也會遇到TCP相關的理論內容。幾乎所有老師在介紹TCP時,都會先丟擲如下定義:

TCP是一種面向連線的,可靠的基於位元組流的傳輸層通訊協議。

這個定義中有一些關鍵字:面向連線可靠的基於位元組流。面向連線和基於位元組流是指什麼?可靠性又是如何保障的?這正是本文要討論的核心。

首先,我們先從理論上,對TCP做一些簡單的介紹。

1.關於面向連線

面向連線主要是指兩個TCP的應用在彼此交換資料之前,都需要先建立一個TCP的連線,形象一些描述這就好比在客戶端和服務端通訊前先建立一條網路上的通道,之後的通訊都基於這個通道進行。通道可以建立,那麼同樣也可以關閉,當兩臺通訊的裝置不再需要互動資料時,就可以關閉此TCP通道。後面我們將介紹連線是如何建立的又如何斷開。

2.關於可靠性

說到可靠性,是指上層業務無需關心資料傳送過程中是否丟失了,對方是否確定完整的收到了等等。例如我們在傳送HTTP請求和接收回執資料時,根本沒有關心資料到達和完整性問題,只需要等待回執即可。這是因為傳輸的可靠性在TCP一層保證了。

TCP在傳送應用資料時會將要傳送的資料分成合適傳送的資料塊,分塊進行傳送。

當TCP傳送了一段資料後,會等待目的端的確認收到報文,如果在一定時間內沒有收到此報文,則會進入超時或重試邏輯,TCP通過確認報文來保證資料的到達可靠性。

TCP將維護一個端到端的資料校驗和,用來檢測在傳輸過程中是否產生了差錯,如果發現了差錯,TCP接收端將丟棄這個報文,並不傳送確認報文,等待對方的超時或重發邏輯。

TCP是通過網路層的IP協議做資料傳輸的,IP資料報是有可能發生亂序的,因此TCP要對接收到的資料進行重排,將收到的資料以正確的順序返回給應用層。

同樣IP資料報也有可能會產生重複,TCP也會負責對重複的資料報進行去重。

最後,TCP也提供流量控制,增加傳輸可靠性。

3.關於基於位元組流

TCP在傳輸資料時,對應用層的資料不做任何解釋,TCP也不再贏輸資料位元組流中插入任何識別符號。這也就是說,TCP具體傳輸的是什麼格式的應用資料在TCP層並不做解析,傳送方傳送的位元組流資料也同樣會在接收方完全相同的接收到,所有解釋和理解都在應用層。

二.巨集觀的看TCP通訊過程

前面有提到,TCP在傳輸應用資料前首先需要建立連線,TCP建立連線的方式是通過3次通訊完成,形象的被稱為TCP的3次握手。

在TCP協議中,有一個Flags欄位,這個欄位將由始至終的貫穿我們理解TCP協議全部過程。此欄位將表示當前TCP報文的型別,有如下6種:

SYN:建立連線。

FIN:斷開連線。

ACK:回執響應。

PSH:資料傳輸。

RST:連線重置。

URG:緊急指標。

這並不是說每個TCP的報文只能對應唯一的一個型別,Flags欄位佔6位的資料,每一位對應一個狀態,報文狀態是可以聚合的,比如一個TCP報文即標記是ACK由標記為PSH。通過聚合可以減少TCP報文數,提高傳輸效率。完整的TCP報文格式如下圖所示:

完整的報文意義我們暫且按下不表,只看其中的Flags部分,可以看到它佔了6位,第1位為URG位,最後一位為FIN位。

1.連線建立過程

TCP通訊是在網路兩端間進行的,要建立連線,一端首先需要發起建連,在HTTP資料請求中,是由客戶端首先發起建連。

第一步:由客戶端發起SYN型別的TCP報,其中會指定目標伺服器的埠號等資料。

第二步:服務端收到客戶端的建連報文後,回覆一個包含ACK和SYN的報文,表示收到客戶端的建連請求,並且發起服務端的建連要求。

第三步:客戶端收到服務端的響應報文後,再回一個ACK型別的報文表示收到,連線建立完成。

2.應用資料的傳送過程

連線建立完成之後,TCP的通訊過程就變得相對簡單。一方傳送資料時,會發出型別的PSH的報文,另一方接收到後需要回復對應的ACK報文,如此迴圈往復,直到資料互動完成。

3.連線斷開過程

與連線的建立類似,斷開連線也需要ACK進行確認。以HTTP請求為例:

第一步:當服務端傳送完資料後,會首先發起FIN型別的報文來斷開連線。

第二步:客戶端收到服務端的FIN報文後,回覆ACK。

第三步:客戶端傳送FIN報文來斷開客戶端連線。

第四步:服務端收到客戶端的FIN報文後,回覆ACK。

因此,TCP斷開連線的過程也被形象的稱為4次揮手。

三.深入理解下TCP的工作流程

現在,雖然巨集觀上我們對TCP的通訊過程有了大致的概念,但還是太膚淺了,許多核心點我們都還沒有涉及。比如時序,可靠性,TCP首部欄位意義等。本節也詳細討論下這部分內容。

1.TCP報文首部詳解

回到上面的那張TCP報文圖示。下面我們來詳細介紹下。

Source Port:源埠,佔16位(兩個位元組),這個欄位很好理解,即發出此報文的埠。

Destination Port:目的埠,佔16位(兩個位元組),即要接收此報文的埠。

Squence Number:序列號,佔32位,TCP是基於位元組流傳輸的,這個序列號用來標識當前報文中第1個數據位元組的編號,使用此序列號對傳送的位元組進行計數,貫穿整個通訊過程。後面會詳細介紹。

Acknowledgment Number:Ack序列號,佔32位,表示傳送ACK的一方期望接收的下個數據位元組的編號。

Data Offset:資料偏移量,佔4位,也可以理解為TCP頭部的長度,用來標記資料配置。其表示TCP頭部佔了多少個4位元組。由於4位的最大數為15,所以TCP報頭的最大長度為4*15=60個位元組。

Reserved:預留欄位,長度為6位。

Flags:型別欄位,佔6位。從低到高依次表示FIN,SYN,RST,PSH,ACK,URG。

Window:佔16位,表示滑動視窗的大小,用來告訴傳送端接收端的快取大小。達到流量控制,最大值為65535。

Checksum:校驗和,佔16位,用來校驗TCP頭資訊傳輸過程中是否出錯。

Urgent Pointer:緊急指標,型別為URG報文有效,表示第一個緊急資料位元組所在位置。

Options-Padding:額外選項,長度可變,當不足32位的倍數時,使用0補齊。

Data:長度可變,傳輸的上層資料,可以為空。

2.時序是如何保障的

保障時序是TCP可靠性的重要目標。時序的保障主要是通過TCP報文頭中的SN(序列號)和AN(Ack序列號)保障的。

我們先來看SN,SN是一個相對的概念,在一個TCP連線要建立時,客戶端發起的第一個SYN報文會被分配一個SN序號,這個序號為初始化序號,之後在本次TCP通訊中,我們都將以這個初始化序號為標準來計算相對SN。需要注意,SYN報文也會佔據一個SN序號,下次傳送資料時,SN序號會被加1。

我們再來看AN,AN表示我預期要接收的下一個資料的SN號,當接收到收到了傳送端的資料後,會回執Ack型別的報文,並將AN設定為傳送端配置的SN號加1。

通過SN和AN,簡化的通訊過程如下圖所以:

此圖看上去是有一些繞,多分析幾遍,你會對TCP的原理有相對透徹的理解。

3.有關可靠性的一些其他技術手段

超時與重試

在一切順利的情況下,TCP建連只需要3步即可完成。但是事實上,現實中的網路環境要複雜的多,建連並不總是順利的。

一種情況是客戶端要連線的埠在服務端並沒有監聽,此時服務端主機TCP服務會直接回執一個RST型別的報文,表示連接出錯,此時發起方應直接關掉連線。

另一種情況是服務端主機處於異常狀態,可能網路出現的問題,此時發起方無法等到服務端的任何回執,此時會進入TCP的建連超時邏輯,TCP的第一次建連重試會在超時約6秒時觸發,第二次重試會在間隔大約24秒後觸發,第三次重試會在間隔大約76秒後觸發。具體遵循的超時重試演算法我們這裡不再展開。

RST型別的復位報文

有許多場景可能觸發RST復位報文,一種是我們訪問了無效埠時,服務端會返回RST報文。

另一種場景是如果連線已經被關閉,再次通過此連線傳送資料,會收到RST的復位連線。這種場景很常見,例如一端由於某些原因已經重新啟動,此時另一端並不知道對端發生了異常,再用舊的連線傳送資料時就會收到RST報文。

四.從實踐中驗證理論

說了這麼多理論,理論部分我們已經介紹了很多,足夠理解TCP的通訊過程。下面我們可以通過實踐來驗證下。

首先可以本地編寫兩個簡單的Socket服務端與客戶端程式。關於示例程式,我們在前面的HTTP一文中已經有介紹,直接使用之前的那些示例程式即可。使用Wireshark抓取本地的TCP報文,客戶端與服務端完整的TCP通訊報文如下圖所示:

我們程式所編寫的業務邏輯如下:

1.客戶端埠號為52079。

2.服務端埠號為9001。

3.由客戶端先發起TCP建連。

4.由客戶端傳送”TCP Customer“資料到服務端。

5.服務端接收到客戶端傳送的資料後,傳送”Hello World“資料給客戶端,之後發起關閉連線。

6.客戶端收到服務端返回的資料後,關閉連線。

在這一通訊過程中,產生了12個TCP報文,我們可以逐個分析下。

第1個報文資料如下:

可以看到,此報文是SYN型別的建連報文,由客戶端發起,SN為3069091127,AN為0。除了一些選項之外,沒有包含任何業務資料。

第2個報文資料如下:

此報文是由服務端發起的Ack報文,同時也是服務端的SYN建連報文。此報文的AN為3069091128,SN為3416281738。

第3個報文資料如下:

此報文由客戶端發出,型別為ACK,SN為3069091128,AN為3416281739。可以看到SN已經增加了,與第2個報文的AN是對應的。

第4個報文資料如下:

此報文型別為ACK,它是一個特殊的報文,可以看到其有服務端傳送給客戶端,Window欄位值為6379,這個報文的用途是指定快取大小。此報文的AN為3069091128,SN為3416281739。SN與第3個報文的AN相對應。

第5個報文資料如下:

此報文由客戶端發出,型別為PSH,即它是一個數據傳輸報文,也可以看到,其內容帶了12個位元組的業務資料,這12個位元組的資料即是”TCP Customer“。SN為3069091128,AN為3416281739。

第6個報文為:

此報文為服務端發出的ACK報文,用來確認客戶端發出的12個位元組的資料,其AN為3069091140,SN為3416281739。

第7個報文如下:

可以看到,這第7個報文即是服務端主動給客戶端發的資料報文,其資料部分長度為13個位元組,即"HelloWorld!\r\n"。其AN為3069091140,SN為3416281739。

第8個報文如下:

此報文為客戶端回執的ACK報文,SN為3069091140,AN為3416281752。

第9到第12個報文為TCP斷開連線的4個報文,SN與AN的邏輯與建連類似,這裡不再分析。

五.結尾

你可能發現了,除了初始建連報文,每個TCP報文都有ACK型別,這是因為ACK位都被置為1並無額外的資料和效能消耗。其實本文只是從理解層面介紹了TCP協議,若要將TCP講完整,需要一本書的厚度也不為過。相信如果你之前對日常天天使用的網路技術並不理解,並且從本系列部落格的首篇閱讀到此,那麼從應用上來講,你一定有了更深一層的理解。後面我們將繼續向下,討論網路層的協議。

專注技術,熱愛生活,交流技術,也做朋友。

——琿少 QQ:316045346