網絡原理:史上最全 tcp-ip 協議詳解

TCP/IP 協議包含了一系列的協議,也叫 TCP/IP 協議族(TCP/IP Protocol Suite,或 TCP/IP Protocols),簡稱 TCP/IP。TCP/IP 協議族提供了點對點的連結機制,並且將傳輸數據幀的封裝、尋址、傳輸、路由以及接收方式,都予以標準化。

TCP/IP 協議的分層模型


在展開介紹 TCP/IP 協議之前,首先介紹一下七層 ISO 模型。國際標準化組織 ISO 爲了使網絡應用更爲普及,推出了 OSI 參考模型,即開放式系統互聯(Open System Interconnect)模型,一般都叫 OSI 參考模型。OSI 參考模型是 ISO 組織在 1985 年發佈的網絡互連模型,其含義就是爲所有公司使用一個統一的規範來控制網絡,這樣所有公司遵循相同的通信規範,網絡就能互聯互通了。

OSI 模型的七層框架

OSI 模型定義了網絡互連的七層框架(物理層、數據鏈路層、網絡層、傳輸層、會話層、表示層、應用層),每一層實現各自的功能和協議,並完成與相鄰層的接口通信。OSI 模型各層的通信協議,大致舉例如下表所示:

表:OSI 模型各層的通信協議舉例

TCP/IP 協議是 Internet 互聯網最基本的協議,其在一定程度上參考了七層 ISO 模型。OSI 模型共有七層,從下到上分別是物理層、數據鏈路層、網絡層、運輸層、會話層、表示層和應用層。但是這顯然是有些複雜的,所以在 TCP/IP 協議中,七層被簡化爲了四個層次。TCP/IP 模型中的各種協議,依其功能不同,被分別歸屬到這四層之中,常被視爲是簡化過後的七層 OSI 模型。

TCP/IP 協議與七層 ISO 模型的對應關係

TCP/IP 協議與七層 ISO 模型的對應關係,大致如下圖所示:

TCP/IP 協議的應用層的主要協議有 HTTP、Telnet、FTP、SMTP 等,是用來讀取來自傳輸層的數據或者將數據傳輸寫入傳輸層;傳輸層的主要協議有 UDP、TCP,實現端對端的數據傳輸;網絡層的主要協議有 ICMP、IP、IGMP,主要負責網絡中數據包的傳送等;鏈路層有時也稱作數據鏈路層或網絡接口層,主要協議有 ARP、RARP,通常包括操作系統中的設備驅動程序和計算機中對應的網絡接口卡,它們一起處理與傳輸媒介(如電纜或其他物理設備)的物理接口細節。

(一)TCP/IP 協議的應用層

應用層包括所有和應用程序協同工作,並利用基礎網絡交換應用程序的業務數據的協議。一些特定的程序被認爲運行在這個層上,該層協議所提供的服務能直接支持用戶應用。應用層協議包括 HTTP(萬維網服務)、FTP(文件傳輸)、SMTP(電子郵件)、SSH(安全遠程登陸)、DNS(域名解析)以及許多其他協議。

(二)TCP/IP 協議的傳輸層

傳輸層的協議,解決了諸如端到端可靠性問題,能確保數據可靠的到達目的地,甚至能保證數據按照正確的順序到達目的地。傳輸層的主要功能大致如下:

(1)爲端到端連接提供傳輸服務;

(2)這種傳輸服務分爲可靠和不可靠的,其中 TCP 是典型的可靠傳輸,而 UDP 則是不可靠傳輸;

(3)爲端到端連接提供流量控制、差錯控制、QoS(Quality of Service) 服務質量等管理服務。

傳輸層主要有兩個性質不同的協議:TCP 傳輸控制協議和 UDP 用戶數據報協議。

TCP 協議是一個面向連接的、可靠的傳輸協議,它提供一種可靠的字節流,能保證數據完整、無損並且按順序到達。TCP 儘量連續不斷地測試網絡的負載並且控制發送數據的速度以避免網絡過載。另外,TCP 試圖將數據按照規定的順序發送。

UDP 協議是一個無連接的數據報協議,是一個 “盡力傳遞” 和“不可靠”協議,不會對數據包是否已經到達目的地進行檢查,並且不保證數據包按順序到達。

總體來說,TCP 協議傳輸效率低,但可靠性強;UDP 協議傳輸效率高,但可靠性略低,適用於傳輸可靠性要求不高、體量小的數據(比如 QQ 聊天數據)。

(三)TCP/IP 協議的網絡層

TCP/IP 協議網絡層的作用是在複雜的網絡環境中爲要發送的數據報找到一個合適的路徑進行傳輸。簡單來說,網絡層負責將數據傳輸到目標地址,目標地址可以是多個網絡通過路由器連接而成的某一個地址。另外,網絡層負責尋找合適的路徑到達對方計算機,並把數據幀傳送給對方,網絡層還可以實現擁塞控制、網際互連等功能。網絡層協議的代表包括:ICMP、IP、IGMP 等。

(四)TCP/IP 協議的鏈路層

鏈路層有時也稱作數據鏈路層或網絡接口層,用來處理連接網絡的硬件部分。該層既包括操作系統硬件的設備驅動、NIC(網卡)、光纖等物理可見部分,還包括連接器等一切傳輸媒介。在這一層,數據的傳輸單位爲比特。其主要協議有 ARP、RARP 等。

物理層:使用 MAC 解決設備的身份證問題


通信的原始時代

你是一臺電腦,你的名字叫 A

很久很久之前,你不與任何其他電腦相連接,孤苦伶仃。

直到有一天,你希望與另一臺電腦 B 建立通信,於是你們各開了一個網口,用一根網線連接了起來。

用一根網線連接起來怎麼就能 "通信" 了呢?我可以給你講 IO、講中斷、講緩衝區,但這不是研究網絡時該關心的問題。

如果你糾結,要麼去研究一下操作系統是如何處理網絡 IO 的,要麼去研究一下包是如何被網卡轉換成電信號發送出去的,要麼就僅僅把它當做電腦裏有個小人在開槍吧~

反正,你們就是連起來了,並且可以通信。

有一天,一個新夥伴 C 加入了,但聰明的你們很快發現,可以每個人開兩個網口,用一共三根網線,彼此相連。

隨着越來越多的人加入,你發現身上開的網口實在太多了,而且網線密密麻麻,混亂不堪。(而實際上一臺電腦根本開不了這麼多網口,所以這種連線只在理論上可行,所以連不上的我就用紅色虛線表示了,就是這麼嚴謹哈哈~)

集線器的誕生

於是你們發明了一箇中間設備,你們將網線都插到這個設備上,由這個設備做轉發,就可以彼此之間通信了,本質上和原來一樣,只不過網口的數量和網線的數量減少了,不再那麼混亂。

你給它取名叫集線器,它僅僅是無腦將電信號轉發到所有出口(廣播),不做任何處理,你覺得它是沒有智商的,因此把人家定性在了物理層。

由於轉發到了所有出口,那 BCDE 四臺機器怎麼知道數據包是不是發給自己的呢?

首先,你要給所有的連接到交換機的設備,都起個名字。原來你們叫 ABCD,但現在需要一個更專業的,全局唯一的名字作爲標識,你把這個更高端的名字稱爲 MAC 地址。

你的 MAC 地址是 aa-aa-aa-aa-aa-aa,你的夥伴 b 的 MAC 地址是 bb-bb-bb-bb-bb-bb,以此類推,不重複就好。

這樣,A 在發送數據包給 B 時,只要在頭部拼接一個這樣結構的數據,就可以了。

B 在收到數據包後,根據頭部的目標 MAC 地址信息,判斷這個數據包的確是發給自己的,於是便收下。

其他的 CDE 收到數據包後,根據頭部的目標 MAC 地址信息,判斷這個數據包並不是發給自己的,於是便丟棄。

雖然集線器使整個佈局乾淨不少,但原來我只要發給電腦 B 的消息,現在卻要發給連接到集線器中的所有電腦,這樣既不安全,又不節省網絡資源。

數據鏈路:使用交換機解決 MAC 地址映射問題


集線器的問題

如果把這個集線器弄得更智能一些,只發給目標 MAC 地址指向的那臺電腦,就好了。

交換機的誕生

雖然只比集線器多了這一點點區別,但看起來似乎有智能了,你把這東西叫做交換機。也正因爲這一點點智能,你把它放在了另一個層級,數據鏈路層。

如上圖所示,你是這樣設計的。

交換機內部維護一張 MAC 地址表,記錄着每一個 MAC 地址的設備,連接在其哪一個端口上。

假如你仍然要發給 B 一個數據包,構造瞭如下的數據結構從網口出去。

到達交換機時,交換機內部通過自己維護的 MAC 地址表,發現目標機器 B 的 MAC 地址 bb-bb-bb-bb-bb-bb 映射到了端口 1 上,於是把數據從 1 號端口發給了 B,完事~

你給這個通過這樣傳輸方式而組成的小範圍的網絡,叫做以太網。

當然最開始的時候,MAC 地址表是空的,是怎麼逐步建立起來的呢?

假如在 MAC 地址表爲空是,你給 B 發送瞭如下數據

由於這個包從端口 4 進入的交換機,所以此時交換機就可以在 MAC 地址表記錄第一條數據:

MAC:aa-aa-aa-aa-aa-aa-aa 端口:4

交換機看目標 MAC 地址(bb-bb-bb-bb-bb-bb)在地址表中並沒有映射關係,於是將此包發給了所有端口,也即發給了所有機器。

之後,只有機器 B 收到了確實是發給自己的包,於是做出了響應,響應數據從端口 1 進入交換機,於是交換機此時在地址表中更新了第二條數據:

MAC:bb-bb-bb-bb-bb-bb 端口:1

過程如下:

經過該網絡中的機器不斷地通信,交換機最終將 MAC 地址表建立完畢~

隨着機器數量越多,交換機的端口也不夠了,但聰明的你發現,只要將多個交換機連接起來,這個問題就輕而易舉搞定~

你完全不需要設計額外的東西,只需要按照之前的設計和規矩來,按照上述的接線方式即可完成所有電腦的互聯,所以交換機設計的這種規則,真的很巧妙。你想想看爲什麼(比如 A 要發數據給 F)。

但是你要注意,上面那根紅色的線,最終在 MAC 地址表中可不是一條記錄呀,而是要把 EFGH 這四臺機器與該端口(端口 6)的映射全部記錄在表中。

MAC 地址和端口的映射記錄

最終,兩個交換機將分別記錄 A ~ H 所有機器的映射記錄。

左邊的交換機

右邊的交換機

這在只有 8 臺電腦的時候還好,甚至在只有幾百臺電腦的時候,都還好,所以這種交換機的設計方式,已經足足支撐一陣子了。

但很遺憾,人是貪婪的動物,很快,電腦的數量就發展到幾千、幾萬、幾十萬。

傳輸層:IP 地址和路由器


二層交換機的問題

交換機已經無法記錄如此龐大的映射關係了。

此時你動了歪腦筋,你發現了問題的根本在於,連出去的那根紅色的網線,後面不知道有多少個設備不斷地連接進來,從而使得地址表越來越大。

那我可不可以讓那根紅色的網線,接入一個新的設備,這個設備就跟電腦一樣有自己獨立的 MAC 地址,而且同時還能幫我把數據包做一次轉發呢?

這個設備就是路由器,它的功能就是,作爲一臺獨立的擁有 MAC 地址的設備,並且可以幫我把數據包做一次轉發,你把它定在了網絡層。

注意,路由器的每一個端口,都有獨立的 MAC 地址

好了,現在交換機的 MAC 地址表中,只需要多出一條 MAC 地址 ABAB 與其端口的映射關係,就可以成功把數據包轉交給路由器了,這條搞定。

那如何做到,把發送給 C 和 D,甚至是把發送給 DEFGH.... 的數據包,統統先發送給路由器呢?

不難想到這樣一個點子,假如電腦 C 和 D 的 MAC 地址擁有共同的前綴,比如分別是

C 的 MAC 地址:FFFF-FFFF-CCCC D 的 MAC 地址:FFFF-FFFF-DDDD

那我們就可以說,將目標 MAC 地址爲 FFFF-FFFF-?開頭的,統統先發送給路由器。

這樣是否可行呢?答案是否定的。

IP 地址的誕生

我們先從現實中 MAC 地址的結構入手,MAC 地址也叫物理地址、硬件地址,長度爲 48 位,一般這樣來表示

00-16-EA-AE-3C-40

它是由網絡設備製造商生產時燒錄在網卡的 EPROM(一種閃存芯片,通常可以通過程序擦寫)。

其中前 24 位(00-16-EA)代表網絡硬件製造商的編號,後 24 位(AE-3C-40)是該廠家自己分配的,一般表示系列號。

只要不更改自己的 MAC 地址,MAC 地址在世界是唯一的。形象地說,MAC 地址就如同身份證上的身份證號碼,具有唯一性。

那如果你希望向上面那樣表示將目標 MAC 地址爲 FFFF-FFFF-?開頭的,統一從路由器出去發給某一羣設備(後面會提到這其實是子網的概念),那你就需要要求某一子網下統統買一個廠商製造的設備,要麼你就需要要求廠商在生產網絡設備燒錄 MAC 地址時,提前按照你規劃好的子網結構來定 MAC 地址,並且日後這個網絡的結構都不能輕易改變。

這顯然是不現實的。

於是你發明了一個新的地址,給每一臺機器一個 32 位的編號,如:

11000000101010000000000000000001

你覺得有些不清晰,於是把它分成四個部分,中間用點相連。

11000000.10101000.00000000.00000001

你還覺得不清晰,於是把它轉換成 10 進制。

192.168.0.1

最後你給了這個地址一個響亮的名字,IP 地址。現在每一臺電腦,同時有自己的 MAC 地址,又有自己的 IP 地址,只不過 IP 地址是軟件層面上的,可以隨時修改,MAC 地址一般是無法修改的。

這樣一個可以隨時修改的 IP 地址,就可以根據你規劃的網絡拓撲結構,來調整了。

如上圖所示,假如我想要發送數據包給 ABCD 其中一臺設備,不論哪一臺,我都可以這樣描述,"將 IP 地址爲 192.168.0 開頭的全部發送給到路由器,之後再怎麼轉發,交給它!",巧妙吧。

路由器的誕生

路由器誕生了,專門負責 IP 地址的尋找。那報文交給路由器之後,路由器又是怎麼把數據包準確轉發給指定設備的呢?

別急我們慢慢來。

我們先給上面的組網方式中的每一臺設備,加上自己的 IP 地址

現在兩個設備之間傳輸,除了加上數據鏈路層的頭部之外,還要再增加一個網絡層的頭部。

假如 A 給 B 發送數據,由於它們直接連着交換機,所以 A 直接發出如下數據包即可,其實網絡層沒有體現出作用。

但假如 A 給 C 發送數據,A 就需要先轉交給路由器,然後再由路由器轉交給 C。由於最底層的傳輸仍然需要依賴以太網,所以數據包是分成兩段的。

A ~ 路由器這段的包如下:

路由器到 C 這段的包如下:

好了,上面說的兩種情況(A->B,A->C),相信細心的讀者應該會有不少疑問,下面我們一個個來展開。

子網的由來

A 給 C 發數據包,怎麼知道是否要通過路由器轉發呢?

答案:子網

如果源 IP 與目的 IP 處於一個子網,直接將包通過交換機發出去。

如果源 IP 與目的 IP 不處於一個子網,就交給路由器去處理。

好,那現在只需要解決,什麼叫處於一個子網就好了。

這兩個是我們人爲規定的,即我們想表示,對於 192.168.0.1 來說:

192.168.0.xxx 開頭的,就算是在一個子網,否則就是在不同的子網。

那對於計算機來說,怎麼表達這個意思呢?於是人們發明了子網掩碼的概念

假如某臺機器的子網掩碼定爲 255.255.255.0

這表示,將源 IP 與目的 IP 分別同這個子網掩碼進行與運算 ****,相等則是在一個子網,不相等就是在不同子網,就這麼簡單。

比如

那麼 A 與 B 在同一個子網,C 與 D 在同一個子網,但是 A 與 C 就不在同一個子網,與 D 也不在同一個子網,以此類推。

所以如果 A 給 C 發消息,A 和 C 的 IP 地址分別 & A 機器配置的子網掩碼,發現不相等,則 A 認爲 C 和自己不在同一個子網,於是把包發給路由器,就不管了,之後怎麼轉發,A 不關心。

A 如何知道,哪個設備是路由器?

答案:在 A 上要設置默認網關

上一步 A 通過是否與 C 在同一個子網內,判斷出自己應該把包發給路由器,那路由器的 IP 是多少呢?

其實說發給路由器不準確,應該說 A 會把包發給默認網關。

對 A 來說,A 只能直接把包發給同處於一個子網下的某個 IP 上,所以發給路由器還是發給某個電腦,對 A 來說也不關心,只要這個設備有個 IP 地址就行。

所以默認網關,就是 A 在自己電腦裏配置的一個 IP 地址,以便在發給不同子網的機器時,發給這個 IP 地址。

僅此而已!

路由表的由來(和 Mac 表的由來好像,都是逼出來的)

路由器如何知道 C 在哪裏?

答案:路由表

現在 A 要給 C 發數據包,已經可以成功發到路由器這裏了,最後一個問題就是,路由器怎麼知道,收到的這個數據包,該從自己的哪個端口出去,才能直接(或間接)地最終到達目的地 C 呢。

路由器收到的數據包有目的 IP 也就是 C 的 IP 地址,需要轉化成從自己的哪個端口出去,很容易想到,應該有個表,就像 MAC 地址表一樣。

這個表就叫路由表。

至於這個路由表是怎麼出來的,有很多路由算法,本文不展開,因爲我也不會哈哈~

不同於 MAC 地址表的是,路由表並不是一對一這種明確關係,我們下面看一個路由表的結構。

我們學習一種新的表示方法,由於子網掩碼其實就表示前多少位表示子網的網段,所以如 192.168.0.0(255.255.255.0) 也可以簡寫爲 192.168.0.0/24

這就很好理解了,路由表就表示,192.168.0.xxx 這個子網下的,都轉發到 0 號端口,192.168.1.xxx 這個子網下的,都轉發到 1 號端口。下一跳列還沒有值,我們先不管

配合着結構圖來看(這裏把子網掩碼和默認網關都補齊了)

剛纔說的都是 IP 層,但發送數據包的數據鏈路層需要知道 MAC 地址,可是我只知道 IP 地址該怎麼辦呢?

答案:arp

假如你(A)此時不知道你同伴 B 的 MAC 地址(現實中就是不知道的,剛剛我們只是假設已知),你只知道它的 IP 地址,你該怎麼把數據包準確傳給 B 呢?

答案很簡單,在網絡層,我需要把 IP 地址對應的 MAC 地址找到,也就是通過某種方式,找到 192.168.0.2 對應的 MAC 地址 BBBB。

這種方式就是 arp 協議,同時電腦 A 和 B 裏面也會有一張 arp 緩存表,表中記錄着 IP 與 MAC 地址的對應關係。

一開始的時候這個表是空的,電腦 A 爲了知道電腦 B(192.168.0.2)的 MAC 地址,將會廣播一條 arp 請求,B 收到請求後,帶上自己的 MAC 地址給 A 一個響應。此時 A 便更新了自己的 arp 表。

這樣通過大家不斷廣播 arp 請求,最終所有電腦裏面都將 arp 緩存表更新完整。

整個傳輸過程

好了,總結一下,到目前爲止就幾條規則

從各個節點的視角來看

電腦視角

交換機視角

路由器視角

如果你嗅覺足夠敏銳,你應該可以感受到下面這句話:

網絡層(IP 協議)本身沒有傳輸包的功能,包的實際傳輸是委託給數據鏈路層(以太網中的交換機)來實現的。

涉及到的三張表分別是

這三張表是怎麼來的

知道了以上這些,目前網絡上兩個節點是如何發送數據包的這個過程,就完全可以解釋通了!

參考的網絡拓撲圖

那接下來我們就放上參考的 最後一個網絡拓撲圖吧,請做好 戰鬥 準備!

這時路由器 1 連接了路由器 2,所以其路由表有了下一條地址這一個概念,所以它的路由表就變成了這個樣子。如果匹配到了有下一跳地址的一項,則需要再次匹配,找到其端口,並找到下一跳 IP 的 MAC 地址。

也就是說找來找去,最終必須能映射到一個端口號,然後從這個端口號把數據包發出去。

這時如果 A 給 F 發送一個數據包,能不能通呢?如果通的話整個過程是怎樣的呢?

詳細過程動畫描述:

詳細過程文字描述

  1. 首先 A(192.168.0.1)通過子網掩碼(255.255.255.0)計算出自己與 F(192.168.2.2)並不在同一個子網內,於是決定發送給默認網關(192.168.0.254)

  2. A 通過 ARP 找到 默認網關 192.168.0.254 的 MAC 地址。

  3. A 將源 MAC 地址(AAAA)與網關 MAC 地址(ABAB)封裝在數據鏈路層頭部,又將源 IP 地址(192.168.0.1)和目的 IP 地址(192.168.2.2)(注意這裏千萬不要以爲填寫的是默認網關的 IP 地址,從始至終這個數據包的兩個 IP 地址都是不變的,只有 MAC 地址在不斷變化)封裝在網絡層頭部,然後發包

  1. 交換機 1 收到數據包後,發現目標 MAC 地址是 ABAB,轉發給路由器 1

  2. 數據包來到了路由器 1,發現其目標 IP 地址是 192.168.2.2,查看其路由表,發現了下一跳的地址是 192.168.100.5*

  3. 所以此時路由器 1 需要做兩件事,第一件是再次匹配路由表,發現匹配到了端口爲 2,於是將其封裝到數據鏈路層,最後把包從 2 號口發出去。

  4. 此時路由器 2 收到了數據包,看到其目的地址是 192.168.2.2,查詢其路由表,匹配到端口號爲 1,準備從 1 號口把數據包送出去。

  5. 但此時路由器 2 需要知道 192.168.2.2 的 MAC 地址了,於是查看其 arp 緩存,找到其 MAC 地址爲 FFFF,將其封裝在數據鏈路層頭部,並從 1 號端口把包發出去。

  6. 交換機 3 收到了數據包,發現目的 MAC 地址爲 FFFF,查詢其 MAC 地址表,發現應該從其 6 號端口出去,於是從 6 號端口把數據包發出去。

10.F 最終收到了數據包!** 並且發現目的 MAC 地址就是自己,於是收下了這個包

更詳細且精準的過程:

讀到這相信大家已經很累了,理解上述過程基本上網絡層以下的部分主流程就基本疏通了,如果你想要本過程更爲專業的過程描述,可以在公衆號 "低併發編程" 後臺回覆 "網絡",獲得我模擬這個過程的 Cisco Packet Tracer 源文件。

每一步包的傳輸都會有各層的原始數據,以及專業的過程描述

同時在此基礎之上你也可以設計自己的網絡拓撲結構,進行各種實驗,來加深網絡傳輸過程的理解。

至此,經過物理層、數據鏈路層、網絡層這前三層的協議,以及根據這些協議設計的各種網絡設備(網線、集線器、交換機、路由器),理論上只要擁有對方的 IP 地址,就已經將地球上任意位置的兩個節點連通了。

HTTP 報文傳輸原理


利用 TCP/IP 進行網絡通信時,數據包會按照分層順序與對方進行通信。發送端從應用層往下走,接收端從鏈路層往上走。從客戶端到服務器的數據,每一幀數據的傳輸的順序都爲:應用層 -> 運輸層 -> 網絡層 -> 鏈路層 -> 鏈路層 -> 網絡層 -> 運輸層 -> 應用層。

HTTP 報文傳輸過程

以一個 HTTP 請求的傳輸爲例,請求從 HTTP 客戶端(如瀏覽器)和 HTTP 服務端應用的傳輸過程,大致如下圖所示:

圖:HTTP 請求報文的分層傳輸過程

數據封裝和分用

接下來,爲大家介紹一下數據封裝和分用。

數據通過互聯網傳輸的時候不可能是光禿禿的不加標識,如果這樣數據就會亂。所以數據在發送的時候,需要加上特定標識,加上特定標識的過程叫做數據的封裝,在數據使用的時候再去掉特定標識,去掉特定標識的過程就叫做分用。TCP/IP 協議的數據封裝和分用過程,大致如下圖所示:

圖:TCP/IP 協議的數據封裝和分用過程

在數據封裝時,數據經過每個層都會打上該層特定標識,添加上頭部。

在傳輸層封裝時,添加的報文首部時要存入一個應用程序的標識符,無論 TCP 和 UDP 都用一個 16 位的端口號來表示不同的應用程序,並且都會將源端口和目的端口存入報文首部中。

在網絡層封裝時,IP 首部會標識處理數據的協議類型,或者說標識出網絡層數據幀所攜帶的上層數據類型,如 TCP、UDP、ICMP、IP、IGMP 等等。具體來說,會在 IP 首部中存入一個長度爲 8 位的數值,稱作協議域:1 表示爲 ICMP 協議、2 表示爲 IGMP 協議、6 表示爲 TCP 協議、17 表示爲 UDP 協議、等等。IP 首部還會標識發送方地址(源 IP)和接收方地址(目標 IP)。

在鏈路層封裝時,網絡接口分別要發送和接收 IP、ARP 和 RARP 等多種不同協議的報文,因此也必須在以太網的幀首部中加入某種形式的標識,以指明所處理的協議類型,爲此,以太網的報文幀的首部也有一個 16 位的類型域,標識出以太網數據幀所攜帶的上層數據類型,如 IPv4、ARP、IPV6、PPPoE 等等。

數據封裝和分用的過程大致爲:發送端每通過一層會增加該層的首部,接收端每通過一層則刪除該層的首部。

總體來說,TCP/IP 分層管理、數據封裝和分用的好處:分層之後若需改變相關設計,只需替換變動的層。各層之間的接口部分規劃好之後,每個層次內部的設計就可以自由改動。層次化之後,設計也變得相對簡單:各個層只需考慮分派給自己的傳輸任務。

TCP/IP 與 OSI 的區別主要有哪些呢?除了 TCP/IP 與 OSI 在分層模塊上稍有區別,更重要的區別爲:OSI 參考模型注重 “通信協議必要的功能是什麼”,而 TCP/IP 則更強調 “在計算機上實現協議應該開發哪種程序”。

實際上,在傳輸過程中,數據報文會在不同的物理網絡之間傳遞,還是以一個 HTTP 請求的傳輸爲例,請求在不同物理網絡之間的傳輸過程,大致如下圖所示:

圖:HTTP 請求在不同物理網絡之間的傳輸過程

數據包在不同物理網絡之間的傳輸過程中,網絡層會通過路由器去對不同的網絡之間的數據包進行存儲、分組轉發處理。構造互連網最簡單的方法是把兩個或多個網絡通過路由器進行連接。路由器可以簡單理解爲一種特殊的用於網絡互連的硬件盒,其作用是爲不同類型的物理網絡提供連接:以太網、令牌環網、點對點的鏈接和 FDDI(光纖分佈式數據接口)等等。

物理網絡之間通過路由器進行互連,隨着增加不同類型的物理網絡,可能會有很多個路由器,但是對於應用層來說仍然是一樣的,TCP 協議棧爲大家屏蔽了物理層的複雜性。總之,物理細節和差異性的隱藏,使得互聯網 TCP/IP 傳輸的功能變得非常強大。

接下來,開始爲大家介紹與傳輸性能有密切關係的內容:TCP 傳輸層的三次握手建立連接,四次揮手釋放連接。不過在此之前,還得先介紹一下 TCP 報文協議。

TCP 協議的報文格式


在 TCP/IP 協議棧中,IP 協議層只關心如何使數據能夠跨越本地網絡邊界的問題,而不關心數據如何傳輸。整體 TCP/IP 協議棧,共同配合一起解決數據如何通過許許多多個點對點通路,順利傳輸到達目的地。一個點對點通路被稱爲一 “跳”(hop),通過 TCP/IP 協議棧,網絡成員能夠在許多“跳” 的基礎上建立相互的數據通路。

傳輸層 TCP 協議提供了一種面向連接的、可靠的字節流服務,其數據幀格式,大致如下圖所示:

圖:傳輸層 TCP 協議的數據幀格式

一個傳輸層 TCP 協議的數據幀,大致包含以下字段:

(一)源端口號

源端口號表示報文的發送端口,佔 16 位。源端口和源 IP 地址組合起來,可以標識報文的發送地址。

(二)目的端口號

目的端口號表示報文的接收端口,佔 16 位。目的端口和目的 IP 地址相結合,可以標識報文的接收地址。

TCP 協議是基於 IP 協議的基礎上傳輸的,TCP 報文中的源端口號 + 源 IP,與 TCP 報文中的目的端口號 + 目的 IP 一起,組合起來唯一性的確定一條 TCP 連接。

(三)序號(Sequence Number)

TCP 傳輸過程中,在發送端出的字節流中,傳輸報文中的數據部分的每一個字節都有它的編號。序號(Sequence Number)佔 32 位,發起方發送數據時,都需要標記序號。

序號(Sequence Number)的語義與 SYN 控制標誌(Control Bits)的值有關。根據控制標誌(Control Bits)中的 SYN 是否爲 1,序號(Sequence Number)表達不同的含義:

(1)當 SYN = 1 時,當前爲連接建立階段,此時的序號爲初始序號 ISN((Initial Sequence Number),通過算法來隨機生成序號;

(2)當 SYN = 0 時在數據傳輸正式開始時,第一個報文的序號爲 ISN + 1,後面的報文的序號,爲前一個報文的 SN 值 + TCP 報文的淨荷字節數 (不包含 TCP 頭)。比如,如果發送端發送的一個 TCP 幀的淨荷爲 12byte,序號爲 5,則發送端接着發送的下一個數據包的時候,序號的值應該設置爲 5+12=17。

在數據傳輸過程中,TCP 協議通過序號(Sequence Number)對上層提供有序的數據流。發送端可以用序號來跟蹤發送的數據量;接收端可以用序號識別出重複接收到的 TCP 包,從而丟棄重複包;對於亂序的數據包,接收端也可以依靠序號對其進行排序。

(四)確認序號(Acknowledgment Number)

確認序號(Acknowledgment  Number)標識了報文接收端期望接收的字節序列。如果設置了 ACK 控制位,確認序號的值表示一個準備接收的包的序列碼,注意,它所指向的是準備接收的包,也就是下一個期望接收的包的序列碼。

舉個例子,假設發送端(如 Client)發送 3 個淨荷爲 1000byte、起始 SN 序號爲 1 的數據包給 Server 服務端,Server 每收到一個包之後,需要回復一個 ACK 響應確認數據包給 Client。ACK 響應數據包的 ACK Number 值,爲每個 Client 包的爲 SN + 包淨荷,既表示 Server 已經確認收到的字節數,還表示期望接收到的下一個 Client 發送包的 SN 序號,具體的 ACK 值如下圖左邊的正常傳輸部分所示。

圖:傳輸過程的確認序號(Acknowledgment Number)值示例圖

在上圖的左邊部分,Server 第 1 個 ACK 包的 ACK Number 值爲 1001,是通過 Client 第 1 個包的 SN + 包淨荷 = 1+1000 計算得到,表示期望第 2 個 Client 包的 SN 序號爲 1001;Server 第 2 個 ACK 包的 ACK Number 值爲 2001,爲 Client 第 2 個包的 SN + 包淨荷 = 2001,表示期望第 3 個 Server 包的 SN 爲 2001,以此類推。

如果發生錯誤,假設 Server 在處理 Client 的第二個發送包異常,Server 仍然回覆一個 ACK Number 值爲 1001 的確認包,則 Client 的第二個數據包需要重複發送,具體的 ACK 值如上圖右邊的正常傳輸部分所示。

只有控制標誌的 ACK 標誌爲 1 時,數據幀中的確認序號 ACK Number 纔有效。TCP 協議規定,連接建立後,所有發送的報文的 ACK 必須爲 1,也就是建立連接後,所有報文的確認序號有效。如果是 SYN 類型的報文,其 ACK 標誌爲 0,故沒有確認序號。

(五)頭部長度

該字段佔用 4 位,用來表示 TCP 報文首部的長度,單位是 4bit 位。其值所表示的並不是字節數,而是頭部的所含有的 32bit 的數目(或者倍數),或者 4 個字節的倍數,所以 TCP 頭部最多可以有 60 字節(4*15=60)。沒有任何選項字段的 TCP 頭部長度爲 20 字節,所以其頭部長度爲 5,可以通過 20/4=5 計算得到。

(六)預留 6 位

頭部長度後面預留的字段長度爲 6 位,作爲保留字段,暫時沒有什麼用處。

(七)控制標誌

控制標誌(Control Bits)共 6 個 bit 位,具體的標誌位爲:URG、ACK、PSH、RST、SYN、FIN。6 個標誌位的說明,如下表所示。

表:TCP 報文控制標誌(Control Bits)說明

在連接建立的三次握手過程中,若只是單個 SYN 置位,表示的只是建立連接請求。如果 SYN 和 ACK 同時置位爲 1,表示的建立連接之後的響應。

(八)窗口大小

長度爲 16 位,共 2 個字節。此字段用來進行流量控制。流量控制的單位爲字節數,這個值是本端期望一次接收的字節數。

(九)校驗和計算

長度爲 16 位,共 2 個字節。對整個 TCP 報文段,即 TCP 頭部和 TCP 數據進行校驗和計算,接收端用於對收到的數據包進行驗證。

(十)緊急指針

長度爲 16 米,2 個字節。它是一個偏移量,和 SN 序號值相加表示緊急數據最後一個字節的序號。

以上十項內容是 TCP 報文首部必須的字段,也稱固有字段,長度爲 20 個字節。接下來是 TCP 報文的可選項和填充部分。

(十一)可選項和填充部分

可選項和填充部分的長度爲 4n 字節(n 是整數),該部分是根據需要而增加的選項。如果不足 4n 字節,要加填充位,使得選項長度爲 32 位(4 字節)的整數倍,具體的做法是在這個字段中加入額外的零,以確保 TCP 頭是 32 位(4 字節)的整數倍。

最常見的選項字段是 MSS(Maximum Segment Size 最長報文大小),每個連接方通常都在通信的第一個報文段(SYN 標誌爲 1 的那個段)中指明這個選項字段,表示當前連接方所能接受的最大報文段的長度。

由於可選項和填充部分不是必須的,所以 TCP 報文首部最小長度爲 20 個字節。

至此,TCP 報文首部的字段,就全部介紹完了。TCP 報文首部的後面,接着的是數據部分,不過數據部分是可選的。在一個連接建立和一個連接終止時,雙方交換的報文段僅有 TCP 首部。如果一方沒有數據要發送,也使用沒有任何數據的首部來確認收到的數據,比如在處理超時的過程中,也會發送不帶任何數據的報文段。

總體來說,TCP 協議的可靠性,主要通過以下幾點來保障:

(1)應用數據分割成 TCP 認爲最適合發送的數據塊。這部分是通過 MSS(最大數據包長度)選項來控制的,通常這種機制也被稱爲一種協商機制,MSS 規定了 TCP 傳往另一端的最大數據塊的長度。值得注意的是,MSS 只能出現在 SYN 報文段中,若一方不接收來自另一方的 MSS 值,則 MSS 就定爲 536 字節。一般來講,MSS 值還是越大越好,這樣可以提高網絡的利用率。

(2)重傳機制。設置定時器,等待確認包,如果定時器超時還沒有收到確認包,則報文重傳。

(3)對首部和數據進行校驗。

(4)接收端對收到的數據進行排序,然後交給應用層。

(5)接收端丟棄重複的數據。

(6)TCP 還提供流量控制,主要是通過滑動窗口來實現流量控制。

至此,TCP 協議的數據幀格式介紹完了。接下來開始爲大家重點介紹:TCP 傳輸層的三次握手建立連接,四次揮手釋放連接。

TCP 的三次握手


TCP 連接的建立時,雙方需要經過三次握手,而斷開連接時,雙方需要經過四次分手,那麼,其三次握手和四次分手分別做了什麼呢?又是如何進行的呢?

三次握手過程

TCP 連接的建立時,雙方需要經過三次握手,具體過程如下:

(1)第一次握手:Client 進入 SYN_SENT 狀態,發送一個 SYN 幀來主動打開傳輸通道,該幀的 SYN 標誌位被設置爲 1,同時會帶上 Client 分配好的 SN 序列號,該 SN 是根據時間產生的一個隨機值,通常情況下每間隔 4ms 會加 1。除此之外,SYN 幀還會帶一個 MSS(最大報文段長度)可選項的值,表示客戶端發送出去的最大數據塊的長度。

(2)第二次握手:Server 端在收到 SYN 幀之後,會進入 SYN_RCVD 狀態,同時返回 SYN+ACK 幀給 Client,主要目的在於通知 Client,Server 端已經收到 SYN 消息,現在需要進行確認。Server 端發出的 SYN+ACK 幀的 ACK 標誌位被設置爲 1,其確認序號 AN(Acknowledgment Number)值被設置爲 Client 的 SN+1;SYN+ACK 幀的 SYN 標誌位被設置爲 1,SN 值爲 Server 端生成的 SN 序號;SYN+ACK 幀的 MSS(最大報文段長度)表示的是 Server 端的最大數據塊長度。

(3)第三次握手:Client 在收到 Server 的第二次握手 SYN+ACK 確認幀之後,首先將自己的狀態會從 SYN_SENT 變成 ESTABLISHED,表示自己方向的連接通道已經建立成功,Client 可以發送數據給 Server 端了。然後,Client 發 ACK 幀給 Server 端,該 ACK 幀的 ACK 標誌位被設置爲 1,其確認序號 AN(Acknowledgment  Number)值被設置爲 Server 端的 SN 序列號 + 1。還有一種情況,Client 可能會將 ACK 幀和第一幀要發送的數據,合併到一起發送給 Server 端。

(4)Server 端在收到 Client 的 ACK 幀之後,會從 SYN_RCVD 狀態會進入 ESTABLISHED 狀態,至此,Server 方向的通道連接建立成功,Server 可以發送數據給 Client,TCP 的全雙工連接建立完成。

三次握手的圖解

三次握手的交互過程,具體如下圖所示:

圖:TCP 建立的連接時三次握手示意圖

Client 和 Server 完成了三次握手後,雙方就進入了數據傳輸的階段。數據傳輸完成後,連接將斷開,連接斷開的過程需要經歷四次揮手。

TCP 的四次揮手


業務數據通信完成之後,TCP 連接開始斷開(或者拆接)的過程,在這個過程中連接的每個端的都能獨立地、主動的發起,斷開的過程 TCP 協議使用了四路揮手操作。

四次揮手具體過程

四次揮手具體過程,具體如下:

(1)第一次揮手:主動斷開方(可以是客戶端,也可以是服務器端),向對方發送一個 FIN 結束請求報文,此報文的 FIN 位被設置爲 1,並且正確設置 Sequence Number(序列號)和 Acknowledgment Number(確認號)。發送完成後,主動斷開方進入 FIN_WAIT_1 狀態,這表示主動斷開方沒有業務數據要發送給對方,準備關閉 SOCKET 連接了。

(2)第二次揮手:正常情況下,在收到了主動斷開方發送的 FIN 斷開請求報文後,被動斷開方會發送一個 ACK 響應報文,報文的 Acknowledgment Number(確認號)值爲斷開請求報文的 Sequence Number(序列號)加 1,該 ACK 確認報文的含義是:“我同意你的連接斷開請求”。之後,被動斷開方就進入了 CLOSE-WAIT(關閉等待)狀態,TCP 協議服務會通知高層的應用進程,對方向本地方向的連接已經關閉,對方已經沒有數據要發送了,若本地還要發送數據給對方,對方依然會接受。被動斷開方的 CLOSE-WAIT(關閉等待)還要持續一段時間,也就是整個 CLOSE-WAIT 狀態持續的時間。

主動斷開方在收到了 ACK 報文後,由 FIN_WAIT_1 轉換成 FIN_WAIT_2 狀態。

(3)第三次揮手:在發送完成 ACK 報文後,被動斷開方還可以繼續完成業務數據的發送,待剩餘數據發送完成後,或者 CLOSE-WAIT(關閉等待)截止後,被動斷開方會向主動斷開方發送一個 FIN+ACK 結束響應報文,表示被動斷開方的數據都發送完了,然後,被動斷開方進入 LAST_ACK 狀態。

(4)第四次揮手:主動斷開方收在到 FIN+ACK 斷開響應報文後,還需要進行最後的確認,向被動斷開方發送一個 ACK 確認報文,然後,自己就進入 TIME_WAIT 狀態,等待超時後最終關閉連接。處於 TIME_WAIT 狀態的主動斷開方,在等待完成 2MSL 的時間後,如果期間沒有收到其他報文,則證明對方已正常關閉,主動斷開方的連接最終關閉。

被動斷開方在收到主動斷開方的最後的 ACK 報文以後,最終關閉了連接,自己啥也不管了。

四次揮手圖解

四次揮手的全部交互過程,具體如下圖所示:

圖:TCP 建立的連接時四次揮手的示意圖

處於 TIME_WAIT 狀態的主動斷開方,在等待完成 2MSL 的時間後,才真正關閉連接通道,其等待的時間爲什麼是 2MSL 呢?

2MSL 翻譯過來就是兩倍的 MSL。MSL 全稱爲 Maximum Segment Lifetime,指的是一個 TCP 報文片段在網絡中最大的存活時間,具體來說,2MSL 對應於一次消息的來回(一個發送和一個回覆)所需的最大時間。如果直到 2MSL,主動斷開方都沒有再一次收到對方的報文(如 FIN 報文),則可以推斷 ACK 已經被對方成功接收,此時,主動斷開方將最終結束自己的 TCP 連接。所以,TCP 的 TIME_WAIT 狀態也稱爲 2MSL 等待狀態。

有關 MSL 的具體的時間長度,在 RFC1122 協議中推薦爲 2 分鐘。在 SICS(瑞典計算機科學院)開發的一個小型開源的 TCP/IP 協議棧——LwIP 開源協議棧中 MSL 默認爲 1 分鐘。在源自 Berkeley 的 TCP 協議棧實現中 MSL 默認長度爲 30 秒。總體來說,TIME_WAIT(2MSL)等待狀態的時間長度,一般維持在 1-4 分鐘之間。

通過三次握手建立連接和四次揮手拆除連接,一次 TCP 的連接建立及拆除,至少進行 7 次通信,可見其成本是很高的。

三次握手、四次揮手的常見面試題


有關 TCP 的連接建立的三次握手及拆除過程的四次揮手的面試問題,是技術面試過程中的出現頻率很高的重點和難點問題,常見問題大致如下:

問題(1):爲什麼關閉連接的需要四次揮手,而建立連接卻只要三次握手呢?

關閉連接時,被動斷開方在收到對方的 FIN 結束請求報文時,很可能業務數據沒有發送完成,並不能立即關閉連接,被動方只能先回復一個 ACK 響應報文,告訴主動斷開方:“你發的 FIN 報文我收到了,只有等到我所有的業務報文都發送完了,我才能真正的結束,在結束之前,我會發你 FIN+ACK 報文的,你先等着”。所以,被動斷開方的確認報文,需要拆開成爲兩步,故總體就需要四步揮手。

而在建立連接場景中,Server 端的應答可以稍微簡單一些。當 Server 端收到 Client 端的 SYN 連接請求報文後,其中 ACK 報文表示對請求報文的應答,SYN 報文用來表示服務端的連接也已經同步開啓了,而 ACK 報文和 SYN 報文之間,不會有其他報文需要發送,故而可以合二爲一,可以直接發送一個 SYN+ACK 報文。所以,在建立連接時,只需要三次握手即可。

問題(2):爲什麼連接建立的時候是三次握手,可以改成兩次握手嗎?

三次握手完成兩個重要的功能:一是雙方都做好發送數據的準備工作,而且雙方都知道對方已準備好;二是雙方完成初始 SN 序列號的協商,雙方的 SN 序列號在握手過程中被髮送和確認。

如果把三次握手改成兩次握手,可能發生死鎖。兩次握手的話,缺失了 Client 的二次確認 ACK 幀,假想的 TCP 建立的連接時二次揮手,可以如下圖所示:

圖:假想的 TCP 建立的連接時二次握手的示意圖

在假想的 TCP 建立的連接時二次握手過程中,Client 發送 Server 發送一個 SYN 請求幀,Server 收到後發送了確認應答 SYN+ACK 幀。按照兩次握手的協定,Server 認爲連接已經成功地建立了,可以開始發送數據幀。這個過程中,如果確認應答 SYN+ACK 幀在傳輸中被丟失,Client 沒有收到,Client 將不知道 Server 是否已準備好,也不知道 Server 的 SN 序列號,Client 認爲連接還未建立成功,將忽略 Server 發來的任何數據分組,會一直等待 Server 的 SYN+ACK 確認應答幀。而 Server 在發出的數據幀後,一直沒有收到對應的 ACK 確認後就會產生超時,重複發送同樣的數據幀。這樣就形成了死鎖。

問題(3):爲什麼主動斷開方在 TIME-WAIT 狀態必須等待 2MSL 的時間?

原因之一:主動斷開方等待 2MSL 的時間,是爲了確保兩端都能最終關閉。假設網絡是不可靠的,被動斷開方發送 FIN+ACK 報文後,其主動方的 ACK 響應報文有可能丟失,這時候的被動斷開方處於 LAST-ACK 狀態的,由於收不到 ACK 確認被動方一直不能正常的進入 CLOSED 狀態。在這種場景下,被動斷開方會超時重傳 FIN+ACK 斷開響應報文,如果主動斷開方在 2MSL 時間內,收到這個重傳的 FIN+ACK 報文,會重傳一次 ACK 報文,後再一次重新啓動 2MSL 計時等待,這樣,就能確保被動斷開方能收到 ACK 報文,從而能確保被動方順利進入到 CLOSED 狀態。只有這樣,雙方都能夠確保關閉。反過來說,如果主動斷開方在發送完 ACK 響應報文後,不是進入 TIME_WAIT 狀態去等待 2MSL 時間,而是立即釋放連接,則將無法收到被動方重傳的 FIN+ACK 報文,所以不會再發送一次 ACK 確認報文,此時處於 LAST-ACK 狀態的被動斷開方,無法正常進入到 CLOSED 狀態。

原因之二:防止 “舊連接的已失效的數據報文” 出現在新連接中。主動斷開方在發送完最後一個 ACK 報文後,再經過 2MSL,才能最終關閉和釋放端口,這就意味着,相同端口的新 TCP 新連接,需要在 2MSL 的時間之後,才能夠正常的建立。2MSL 這段時間內,舊連接所產生的所有數據報文,都已經從網絡中消失了,從而,確保了下一個新的連接中不會出現這種舊連接請求報文。

問題(4):如果已經建立了連接,但是 Client 端突然出現故障了怎麼辦?

TCP 還設有一個保活計時器,Client 端如果出現故障,Server 端不能一直等下去,這樣會浪費系統資源。每收到一次 Client 客戶端的數據幀後,Server 端都的保活計時器會復位。計時器的超時時間通常是設置爲 2 小時,若 2 小時還沒有收到 Client 端的任何數據幀,Server 端就會發送一個探測報文段,以後每隔 75 秒鐘發送一次。若一連發送 10 個探測報文仍然沒反應,Server 端就認爲 Client 端出了故障,接着就關閉連接。如果覺得保活計時器的兩個多小時的間隔太長,可以自行調整 TCP 連接的保活參數。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/Hgs65tiB-qdJcEzOZ-nb3g