第六章 傳輸層-Transport Layer(下)
上一篇文章(傳送門:這里)對傳輸層的尋址方式、功能、以及流量控制方法做了簡短的介紹,這一部分將介紹傳輸層最重要的兩個實例:TCP協議和UDP協議,看一看之前描述的傳輸層要素是如何應用於TCP、UDP協議之中,並實現他們各自特點的。這一章中,讀者應該重點關注TCP與UDP的區別,以及TCP是如何實現滑動窗口、擁塞控制的。
6.4 Internet傳輸協議:UDP/User Datagram Protocol
6.4.1UDP特點
上圖是UDP的頭。UDP的頭很短,只包含源和目標端口號、長度和校驗和四個項。
- UDP是一種無流控、無連接、不可靠的報文流(對比TCP)
- 目標端口號一共有16位長,用於區分不同進程
- UDP沒有包括流量控制、擁塞控制或者是重傳機制。所有的這些都需要用戶進程自己完成。而UDP本身只是提供了一個與IP協議的接口。
- UDP偽頭:是一個虛擬的數據結構,不是UDP頭中的實際部分。偽頭的存在主要是為了計算校驗和,保證到達的UDP包確實是發送給自己的(因為UDP頭中只含有端口信息,並沒有IP地址)。這么做的原因詳見6.7Faq。TCP和UDP都有偽頭
下面介紹兩個使用了UDP的經典協議。
6.4.2 遠程過程調用:Remote Produce Call
- 允許一台計算機上的進程調用另外一台計算機上的進程
- 是一個分布式C-S系統的例子
6.4.3 實時傳輸協議: Realtime Transport Protocol
- 應用於多媒體應用,如網絡電台、視頻會議RTP的頭設有一個序號(Sequence Number)字段,解決了UDP發送無序的問題
- RTP位於應用層,但是使用了UDP的傳輸協議
6.5 Internet傳輸協議:TCP/ Transport Control Protocol(重要!)
終於到了本章最后一個也是最重要的部分:TCP協議。我們之前已經簡述了三次握手、AIMD等基本概念,下面將深入的展開這些概念,並明白他們是如可確保TCP連接可靠性的。
- TCP是一種可靠的、面向連接的端對端的字節流(Bytestream)(對比UDP)
6.5.1 TCP服務類型
- TCP連接是全雙工的、不支持組播或者廣播
- TCP的連接是字節流,意味着不保留消息的邊界(6.6會詳細解釋)
6.5.2 TCP報頭(head)
注意關注TCP報頭的以下幾個字段:
-
序號sequence number:用來進行排序,保證數據內容有序
-
序號+確認號:為TCP滑動窗口服務(稍后會提到)
-
TCP頭長度TCP header length:標記TCP頭具有多少個32位信息,以區分報頭和載荷(由於TCP頭有兩個位置是可選長度,故不確定長度)
-
校驗和CheckSum:用於差錯控制,保證可靠性,也有IP偽頭。
-
8位標志位:SYN用於建立連接;ACK是確認;FIN是釋放連接;CWR與ECE用於擁塞控制
-
窗口大小:用於流量控制(滑動窗口)
6.5.3 TCP連接建立
TCP連接建立使用的即是我們之前描述的“三次握手”的過程。注意明確兩個主機之前各自的SEQ和ACK序號是怎么確定的。
6.5.4 TCP連接釋放
TCP的連接釋放屬於對稱釋放,記成“四次握手”
由主機1發送一個斷開連接請求,表示內容已經傳送完畢,主機2確認。主機2隨后也發送一個斷開連接請求,主機1確認后連接正式斷開。
6.5.5 TCP滑動窗口
我們在之前已經講過了鏈路層的滑動窗口。而在傳輸層,TCP的滑動窗口是依靠TCP報頭中的序號字段和確認號字段實現的。通過滑動窗口,TCP可以合理的調整發送的流量,減少擁塞現象的發生。下面用圖片來解釋。
假設接收方擁有一個大小為4kb的緩沖區。傳輸層每次接收到消息之后就會發送至這個緩沖區,同時系統也會以另一個速率從緩沖區讀取信息。流量控制的目的就是維持一個合適的速率使得整個緩沖區既不溢出也不會浪費資源。此時連接已經建立,由發送方先開始發送數據。每次收到數據后,發回的ACK都包含一個字段提示自己的緩沖區余量。如果緩沖區已滿,發送端就會將自己阻塞一段時間。
這里需要特別注意的還是ACK與SEQ字段大小該如何設置。SEQ字段只屬於發送端,其含義大概可以理解為“已經發送了多少數據”,因此其大小等於上一個數據包中收到的ACK大小;而ACK表示的是“已經接受了多少數據”,其大小等於上一個發送端發來的數據包序列號+本次收到的數據大小。
此外,降低TCP性能的另一個原因是低能窗口綜合症。當接收端每次只能讀取很少的字節(假如在上面這個例子中,接收方每次剛剛讀取了0.5k之后就通知發送方),整個系統會產生大量的冗余信息。解決的方法是強制接收方在有了一定大小的空間之后再通知對方。
6.5.6 TCP計時器管理
這一部分的內容主要是關於如何設置一個合適的時間界限判斷數據包是否應該由於超時而被丟棄。
通過上圖我們可以看到,再數據鏈路層中(左圖),數據包到達的時間的概率密度函數相對比較集中,因此很容易設置一個界限來判斷是否超時;但是在網絡層下數據包到達的時間則更加分散,很難設置一個判斷標准。解決的方法是使用一個依據網絡情況的動態算法。
6.5.7 TCP擁塞控制(重要!)
首先簡短回憶一下:網絡層檢測到擁塞后,會通過丟棄數據包來緩解擁塞。在傳輸層,我們可以使用AIMD控制法來優化網絡容量。TCP的擁塞控制就以AIMD為基礎,並加以改進。
擁塞窗口
TCP維護了一個擁塞控制窗口CWND-congestion window,窗口大小是任意時刻,發送端可以向網絡發送的字節數。通過使用窗口大小/連接的往返時間,可以得到發送速率。TCP根據AIMD規則調整CWND大小。除此之外,TCP本來就有一個流量控制窗口RWND,指出了接收端可以接受的最大字節數,TCP最終的可能發送字節數就是兩個窗口中的最小值。
相比起存放在報頭中的RWND,CWND窗口維持在發送端本地,不會隨着數據包發送。
確認時鍾
確認時鍾作用在之前講到的CWND(擁塞控制窗口)上,其目的是提供一個可靠的發送速率。人們根據觀察得出:一條確認消息返回到發送端的速率恰好是數據包通過路徑上最慢鏈路的速率,這個時序就是確認時鍾。通過使用確認時鍾,數據包不會擁塞鏈路上的任意一個路由器,使得TCP能夠平滑輸出流量並且避免不必要的路由器隊列。
慢速啟動-slow start
之后的幾個問題研究的是如何維持一個合適的CWND。盡管AIMD規則提供了一個比較好的思路,但是假如CWND從一個很小的規模就開始應用AIMD,則需要很長的時間才能收斂至最優點。人們因此提出了快速啟動的觀點:每一個被確認的段允許發送兩個段,每經過一個往返時間,擁塞窗口增加一倍。
圖中,RTT表示往返時間。如果數據包在一個往返時間內收到,那么表示當前的網絡狀況為通暢。慢速啟動方式其實並不慢,CWND窗口的增長是指數級的。但相比之前的做法(直接發送RWND窗口大小的數據作為開始點),慢速啟動又相對的慢一些。此外,還有必要為慢速啟動設置一個慢速啟動閾值,一旦超出了閾值,TCP就會從慢速啟動切換到線性增加-additive increase(也就是我們在AIMD中學到的知識)。在每一個RTT末尾,發送端額外注入一個數據包。
快速重傳-fast retransmission
慢速啟動的缺陷是必須設置一個更加保守的超時判斷時間來防止因重復數據包太多導致的網絡擁塞,因此可能會降低網絡效率。使用重復確認(duplicate ack)機制可以部分解決這個問題:當丟失數據包的后續數據包到達接收端時,他們所觸發的返回確認全部擁有相同的確認號。當發送方收到多個確認號相同的數據包后,那么就可以推斷出某個數據包已經丟失(或者至少是超時)。
在TCP中,這個重復確認的數量通常被設置成3。當收到了三個連續的重復確認,發送端默認這個包已經超時,直接重新發送這個數據包(在判斷超時的計時器結束之前),這么一來就縮短了等待時間。這種方法叫做快速重傳。當重新發送之后,發送端會把自己的CWND閾值設置為之前CWND值的一半,並從0開始慢速啟動。
如上圖,慢速啟動的第一個閾值被設置成32KB,並在CWND=40時發生丟包現象。丟包被檢測到之后,確認時鍾停止;新的閾值被設置為40KB 的一半-->20KB;重新從0開始,執行慢速啟動。這是一種階梯狀的模式。
快速恢復-fast recovery
快速恢復是一種新的機制,用於確保擁塞窗口上能夠持續運行確認時鍾。也就是說,除非是第一次啟動,其他時候不會進入慢速啟動,而是直接從閾值開始線性遞增。這是一種鋸齒狀的模式。
現在重新觀察TCP擁塞控制的這張圖片不難發現,其實線性遞增部分就是對應着上一篇博文AIMD中的加法遞增部分,而快速恢復機制對應了AIMD中的乘法遞減部分(乘以1/2)。只不過上一篇中的AIMD圖像表現的是兩個發送端的流量變化關系,而上圖表現的是一個客戶端的流量隨着時間的變化關系(時間是橫坐標)。
6.6 TCP與UDP區別比較
TCP | UDP |
---|---|
TCP面向連接,提供可靠的服務。 意味着TCP會使用校驗和確保數據正確,使用序號確保數據有序到達且不重復,使用ACK機制重新發送丟失的數據包,並且包含連接建立和斷開的過程。 |
UDP提供的是無連接的服務,並不可靠。 UDP不包含序號和ACK重傳等機制。(當應用層擁有一些特殊需求時,應用層可以調整自己的格式以提供一些特殊服務,例如實時傳輸協議RTP) |
TCP具有擁塞控制的功能,使用改進的AIMD規則來減少擁塞 | UDP沒有擁塞控制,當網絡層出現擁塞時,也不會使得源主機的發送速率降低。 |
TCP連接只支持單播,一定是點到點的。 | UDP可以進行單播、多播、廣播。 |
TCP使用字節流,不保留消息邊界。當數據過大的時候會進行拆分。 | UDP面向報文流,會保留應用層提供的消息邊界。 |
TCP的首部更長,傳輸速度相對較慢 | UDP的首部較短,傳輸速度也更快。 |
另外關於報文流和字節流,下圖可以更方便理解。
假如應用層提供了一個2048字節大小的數據等待發送,TCP可能會將其拆分成4個512字節大小的數據段,分別發送給網絡層,接收方再負責組合。真正發送的數據包並不是應用層的數據,因此稱為沒有保留消息邊界。
再來看另外一個例子,當客戶分別發出32B和64B的包,流式的如TCP發送時是將32B+64B=96B的一個包發出,另一邊收到的也是96B的包,TCP不再保留原來數據包的邊界,而是根據協議的需求將數據統一發送;而對於UDP來說,服務器端收到的就是32B和64B的兩個包,保留了從前數據包的邊界。當然,包大小取決於協議和網絡。
6.7 FAQ 常見問題
為什么要區分網絡層和傳輸層?
的確,傳輸層和網絡層有諸多的相似之處,並且共同負責了包括擁塞控制在內的多個問題的解決。但是分出兩個層次依舊是必要的。
一般情況下來說,傳輸層的代碼都運行在用戶的機器上,如PC機,或者服務器;而網絡層的代碼大部分部署在由運營商操作的路由器上。這樣的差別是設計區分這兩個層次的主要原因:用戶對網絡層並沒有真正的控制權,因為他們不擁有路由器;而另一方面,路由器也沒有權限在數據包發生丟失等情況后要求PC機重新發送這個數據包。
UDP和TCP為什么需要偽頭(pseudoheader)?
偽頭存在的目的是為了幫助傳輸層的進程再一次確認該數據包確實屬於這個進程所在的ip地址。問題在於,為什么傳輸層還需要再做一遍網絡層的工作呢?難道到達網絡層的時候不是已經校驗過IP地址的准確性了嗎?理論上來講確實是這樣的。然而在實際應用中確實會發生錯發的現象,網絡層可能會把一個數據包發至錯誤位置。例如路由出錯,或者是數據包的ip頭被修改了(前一章中的NAT盒子就可能會引起這一點)。
傳輸層的校驗和與鏈路層的校驗和有什么關系?
沒有什么關系。傳輸層的內容會被一路封裝下去,成為鏈路層的載荷之一。而校驗和是存在各自的頭部,用於校驗某一層的數據是否正確。比如連鏈路層的校驗和只管鏈路層。當然這兩層可能采取了同一種算法。
傳輸層所謂“面向連接的服務、無連接的服務”與網絡層“面向連接的服務、無連接的服務”之間有什么不可告人的關系?
在傳輸層,我們學到了TCP、UDP兩種有連接和無連接的服務。同樣在網絡層,也存在兩種以有無連接區分的服務數據報和虛電路。這兩層的服務模式有什么關系?傳輸層使用TCP是否意味着網絡層也要使用虛電路?
答案是沒什么關系。舉一個最簡單的例子:在鏈路層我們了解到了有確認無連接的802.3和無確認無連接的802.11。難道使用TCP的數據段不可以在以太網和無線局域網鏈路上傳輸嗎?這顯然是不可能的。
這個問題部分涉及到了計算機網絡分層的原因。在考慮傳輸層功能的時候,我們可以暫時不考慮其底層的網絡層、鏈路層和物理層,直接想象成應用A把數據發送給了一個黑盒,之后黑盒通過某種方法把數據交給應用B。在這個過程中,A和B可以不考慮小黑盒是如何實現的。根據協議的不同,路由器可以把數據交給不同的鏈路傳送,而所謂連接,並不是一定要有一條固定不變的實際線路。實際上,面向連接的服務只意味着協議需要在發送數據之前建立連接,並在傳輸結束折后釋放連接。例如,在傳輸層選擇了TCP協議,意味着傳輸層的兩個應用之間需要向小黑盒發送一系列的建立和釋放連接請求(三次握手和四次握手);而至於網絡層如何發送傳輸層的這些數據、鏈路層如何發送網絡層的這些數據,TCP本身並不關心(甚至也管不着)。當然生活中,我們通常使用TCP/IP協議簇,其中的IP協議是使用數據報的,但並不能理解為“傳輸層有連接的服務在網絡層一定也是有連接的”。歸根結底,傳輸層所謂連接,描述的是一條虛擬的鏈路。其與網絡層連接的交集僅在於涵義上,並不是包含關系。
TCP和TCP/IP有什么不可告人的關系?
我們在第一章學習了TCP/IP。TCP/IP是一個協議簇(彼此相互關聯的一組協議),包含了STMP、TCP、UDP、IP等等經典協議。因為其中的TCP和IP協議最具代表性,因此統稱為TCP/IP。TCP/IP包含了四個層次的協議。