從本章開始,我們開始介紹最重要的傳輸層。傳輸層位于 OSI 七層模型的第四層(由下往上)。顧名思義,傳輸層的主要作用是實現應用程序之間的通信。網絡層主要是保證不同數據鏈路下數據的可達性,至于如何傳輸數據則是由傳輸層負責。
傳輸層協議簡介
常見的傳輸層協議主要有 TCP 協議和 UDP 協議。TCP 協議是面向有連接的協議,也就是說在使用 TCP 協議傳輸數據之前一定要在發送方和接收方之間建立連接。一般情況下建立連接需要三步,關閉連接需要四步。
建立 TCP 連接后,由于有數據重傳、流量控制等功能,TCP 協議能夠正確處理丟包問題,保證接收方能夠收到數據,與此同時還能夠有效利用網絡帶寬。然而 TCP 協議中定義了很多復雜的規范,因此效率不如 UDP 協議,不適合實時的視頻和音頻傳輸。
UDP 協議是面向無連接的協議,它只會把數據傳遞給接收端,但是不會關注接收端是否真的收到了數據。但是這種特性反而適合多播,實時的視頻和音頻傳輸。因為個別數據包的丟失并不會影響視頻和音頻的整體效果。
IP 協議中的兩大關鍵要素是源 IP 地址和目標 IP 地址。而剛剛我們說過,傳輸層的主要作用是實現應用程序之間的通信。因此傳輸層的協議中新增了三個要素:源端口號,目標端口號和協議號。通過這五個信息,可以唯一識別一個通信。
不同的端口用于區分同一臺主機上不同的應用程序。假設你打開了兩個瀏覽器,瀏覽器 A 發出的請求不會被瀏覽器 B 接收,這就是因為 A 和 B 具有不同的端口。
協議號用于區分使用的是 TCP 還是 UDP。因此相同兩臺主機上,相同的兩個進程之間的通信,在分別使用 TCP 協議和 UDP 協議時也可以被正確的區分開來。
用一句話來概括就是:“源 IP 地址,目標 IP 地址,源端口號,目標端口號和協議號”這五個信息只要有一個不同,都被認為是不同的通信。
UDP 首部
UDP 協議最大的特點就是簡單,它的首部如下圖所示:

包長度表示 UDP 首部的長度和 UPD 數據長度之和。
校驗和用來判斷數據在傳輸過程中是否損壞。計算這個校驗和的時候,不僅考慮源端口號和目標端口號,還要考慮 IP 首部中的源 IP 地址,目標 IP 地址和協議號(這些又稱為 UDP 偽首部)。這是因為以上五個要素用于識別通信時缺一不可,如果校驗和只考慮端口號,那么另外三個要素收到破壞時,應用就無法得知。這有可能導致不該收到包的應用收到了包,改收到包的應用反而沒有收到。
這個概念同樣適用于即將介紹的 TCP 首部。
TCP 首部
和 UDP 首部相比,TCP 首部要復雜得多。解析這個首部的時間也相應的會增加,這是導致 TCP 連接的效率低于 UDP 的原因之一。

其中某些關鍵字段解釋如下:
-
序列號:它表示發送數據的位置,假設當前的序列號為 s,發送數據長度為 l,則下次發送數據時的序列號為 s + l。在建立連接時通常由計算機生成一個隨機數作為序列號的初始值。
-
確認應答號:它等于下一次應該接收到的數據的序列號。假設發送端的序列號為 s,發送數據的長度為 l,那么接收端返回的確認應答號也是 s + l。發送端接收到這個確認應答后,可以認為這個位置以前所有的數據都已被正常接收。
-
數據偏移:TCP 首部的長度,單位為 4 字節。如果沒有可選字段,那么這里的值就是 5。表示 TCP 首部的長度為 20 字節。
-
控制位:改字段長度為 8 比特,分別有 8 個控制標志。依次是 CWR,ECE,URG,ACK,PSH,RST,SYN 和 FIN。在后續的文章中你會陸續接觸到其中的某些控制位。
-
窗口大小:用于表示從應答號開始能夠接受多少個 8 位字節。如果窗口大小為 0,可以發送窗口探測。
-
緊急指針:盡在 URG 控制位為 1 時有效。表示緊急數據的末尾在 TCP 數據部分中的位置。通常在暫時中斷通信時使用(比如輸入 Ctrl + C)。
TCP 握手
TCP 是面向有連接的協議,連接在每次通信前被建立,通信結束后被關閉。了解連接建立和關閉的過程通常是考察的重點。連接的建立和關閉過程可以用一張圖來表示:

通常情況下,我們認為客戶端首先發起連接。
三次握手建立連接
這個過程可以用以下三句形象的對話表示:
- (客戶端):我要建立連接了。
- (服務端):我知道你要建立連接了,我這邊沒有問題。
- (客戶端):我知道你知道我要建立連接了,接下來我們就正式開始通信。
為什么是三次握手
根據一般的思路,我們可能會覺得只要兩次握手就可以了,第三步確認看似是多余的。那么 TCP 協議為什么還要費力不討好的加上這一次握手呢?
這是因為在網絡請求中,我們應該時刻記住:“網絡是不可靠的,數據包是可能丟失的”。假設沒有第三次確認,客戶端向服務端發送了 SYN,請求建立連接。由于延遲,服務端沒有及時收到這個包。于是客戶端重新發送一個 SYN 包。回憶一下介紹 TCP 首部時提到的序列號,這兩個包的序列號顯然是相同的。
假設服務端接收到了第二個 SYN 包,建立了通信,一段時間后通信結束,連接被關閉。這時候最初被發送的 SYN 包剛剛抵達服務端,服務端又會發送一次 ACK 確認。由于兩次握手就建立了連接,此時的服務端就會建立一個新的連接,然而客戶端覺得自己并沒有請求建立連接,所以就不會向服務端發送數據。從而導致服務端建立了一個空的連接,白白浪費資源。
在三次握手的情況下,服務端直到收到客戶端的應答后才會建立連接。因此在上述情況下,客戶端會接受到一個相同的 ACK 包,這時候它會拋棄這個數據包,不會和服務端進行第三次握手,因此避免了服務端建立空的連接。
ACK 確認包丟失怎么辦
三次握手其實解決了第二步的數據包丟失問題。那么第三步的 ACK 確認丟失后,TCP 協議是如何處理的呢?
按照 TCP 協議處理丟包的一般方法,服務端會重新向客戶端發送數據包,直至收到 ACK 確認為止。但實際上這種做法有可能遭到 SYN 泛洪攻擊。所謂的泛洪攻擊,是指發送方偽造多個 IP 地址,模擬三次握手的過程。當服務器返回 ACK 后,攻擊方故意不確認,從而使得服務器不斷重發 ACK。由于服務器長時間處于半連接狀態,最后消耗過多的 CPU 和內存資源導致死機。
正確處理方法是服務端發送 RST 報文,進入 CLOSE 狀態。這個 RST 數據包的 TCP 首部中,控制位中的 RST 位被設置為 1。這表示連接信息全部被初始化,原有的 TCP 通信不能繼續進行。客戶端如果還想重新建立 TCP 連接,就必須重新開始第一次握手。
四次握手關閉連接
這個過程可以用以下四句形象的對話表示:
- (客戶端):我要關閉連接了。
- (服務端):你那邊的連接可以關閉了。
- (服務端):我這邊也要關閉連接了。
- (客戶端):你那邊的連接可以關閉了。
由于連接是雙向的,所以雙方都要主動關閉自己這一側的連接。
關閉連接的最后一個 ACK 丟失怎么辦
實際上,在第三步中,客戶端收到 FIN 包時,它會設置一個計時器,等待相當長的一段時間。如果客戶端返回的 ACK 丟失,那么服務端還會重發 FIN 并重置計時器。假設在計時器失效前服務器重發的 FIN 包沒有到達客戶端,客戶端就會進入 CLOSE 狀態,從而導致服務端永遠無法收到 ACK 確認,也就無法關閉連接。
示意圖如下:

原文鏈接:http://www.jianshu.com/p/dc456cf57e06
著作權歸作者所有,轉載請聯系作者獲得授權,并標注“簡書作者”。