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

自上而下的理解網絡(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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章