告知你不為人知的 UDP:疑難雜症和使用【轉載】

告知你不為人知的 UDP:疑難雜症和使用【轉載】

作者介紹:黃日成,手Q遊戲中心後台開發,騰訊高級工程師。從事C++服務後台開發4年多,主要負責手Q遊戲中心後台基礎系統、複雜業務系統開發,主導過手Q遊戲公會、企鵝電競App-對戰系統等項目的後台系統設計,有豐富的後台架構經驗。

引言

作為文章「《從TCP三次握手說起:淺析TCP協議中的疑難雜症》」的姊妹篇,很早就計劃寫篇關於UDP的文章,儘管UDP協議遠沒TCP協議那麼龐大、複雜,但是,要想將UDP描述清楚,用好UDP卻要比TCP難不少,於是文章從下筆寫,到最終寫成,斷斷續續拖了好幾個月。

對應系列的上一篇:《告知你不為人知的UDP:連接性和負載均衡》

3. UDP疑難雜症

3.1 UDP的傳輸方式:面向報文

面向報文的傳輸方式決定了 UDP 的數據發送方式是一份一份的,也就是應用層交給 UDP 多長的報文,UDP 就照樣發送,即一次發送一個報文。那麼UDP的報文大小由哪些影響因素呢? UDP 數據包的理論長度是多少,合適的 UDP 數據包應該是多少呢?

(1) UDP 報文大小的影響因素,主要有以下3個

[1] UDP協議本身,UDP協議中有16位的UDP報文長度,那麼UDP報文長度不能超過2^16=65536。

[2] 乙太網(Ethernet)數據幀的長度,數據鏈路層的MTU(最大傳輸單元)。

[3] socket的UDP發送緩存區大小

(2) UDP數據包最大長度

根據 UDP 協議,從 UDP 數據包的包頭可以看出,UDP 的最大包長度是2^16-1的個位元組。由於UDP包頭佔8個位元組,而在IP層進行封裝後的IP包頭佔去20位元組,所以這個是UDP數據包的最大理論長度是2^16 - 1 - 8 - 20 = 65507位元組。如果發送的數據包超過65507位元組,send或sendto函數會錯誤碼1(Operation not permitted, Message too long),當然啦,一個數據包能否發送65507位元組,還和UDP發送緩衝區大小(linux下UDP發送緩衝區大小為:cat /proc/sys/net/core/wmem_default)相關,如果發送緩衝區小於65507位元組,在發送一個數據包為65507位元組的時候,send或sendto函數會錯誤碼1(Operation not permitted, No buffer space available)。

(3) UDP數據包理想長度

理論上 UDP 報文最大長度是65507位元組,實際上發送這麼大的數據包效果最好嗎?我們知道UDP是不可靠的傳輸協議,為了減少 UDP 包丟失的風險,我們最好能控制 UDP 包在下層協議的傳輸過程中不要被切割。相信大家都知道MTU這個概念。 MTU 最大傳輸單元,這個最大傳輸單元實際上和鏈路層協議有著密切的關係,EthernetII 幀的結構 DMAC + SMAC + Type + Data + CRC 由於乙太網傳輸電氣方面的限制,每個乙太網幀都有最小的大小64位元組,最大不能超過1518位元組,對於小於或者大於這個限制的乙太網幀我們都可以視之為錯誤的數據幀,一般的乙太網轉發設備會丟棄這些數據幀。由於乙太網 EthernetII 最大的數據幀是1518位元組,除去乙太網幀的幀頭(DMAC目的 MAC 地址48bit=6Bytes+SMAC源 MAC 地址48bit=6Bytes+Type域2bytes)14Bytes和幀尾CRC校驗部分4Bytes那麼剩下承載上層協議的地方也就是Data域最大就只能有1500位元組這個值我們就把它稱之為MTU。

在下層數據鏈路層最大傳輸單元是1500位元組的情況下,要想IP層不分包,那麼UDP數據包的最大大小應該是1500位元組 – IP頭(20位元組) – UDP頭(8位元組) = 1472位元組。不過鑒於Internet上的標準MTU值為576位元組,所以建議在進行Internet的UDP編程時,最好將UDP的數據長度控制在 (576-8-20)548位元組以內。

3.2 UDP數據包的發送和接收問題

(1) UDP的通信有界性

在阻塞模式下,UDP的通信是以數據包作為界限的,即使server端的緩衝區再大也要按照client發包的次數來多次接收數據包,server只能一次一次的接收,client發送多少次,server就需接收多少次,即客戶端分幾次發送過來,服務端就必須按幾次接收。

(2) UDP數據包的無序性和非可靠性

client依次發送1、2、3三個UDP數據包,server端先後調用3次接收函數,可能會依次收到3、2、1次序的數據包,收包可能是1、2、3的任意排列組合,也可能丟失一個或多個數據包。

(3) UDP數據包的接收

client發送兩次UDP數據,第一次 500位元組,第二次300位元組,server端阻塞模式下接包,第一次recvfrom( 1000 ),收到是 1000,還是500,還是300,還是其他?

  • 由於UDP通信的有界性,接收到只能是500或300,又由於UDP的無序性和非可靠性,接收到可能是300,也可能是500,也可能一直阻塞在recvfrom調用上,直到超時返回(也就是什麼也收不到)。

在假定數據包是不丟失並且是按照發送順序按序到達的情況下,server端阻塞模式下接包,先後三次調用:recvfrom( 200),recvfrom( 1000),recvfrom( 1000),接收情況如何呢?

  • 由於UDP通信的有界性,第一次recvfrom( 200)將接收第一個500位元組的數據包,但是因為用戶空間buf只有200位元組,於是只會返回前面200位元組,剩下300位元組將丟棄。第二次recvfrom( 1000)將返回300位元組,第三次recvfrom( 1000)將會阻塞。

(4) UDP包分片問題

如果MTU是1500,Client發送一個8000位元組大小的UDP包,那麼Server端阻塞模式下接包,在不丟包的情況下,recvfrom(9000)是收到1500,還是8000。如果某個IP分片丟失了,recvfrom(9000),又返回什麼呢?

根據UDP通信的有界性,在buf足夠大的情況下,接收到的一定是一個完整的數據包,UDP數據在下層的分片和組片問題由IP層來處理,提交到UDP傳輸層一定是一個完整的UDP包,那麼recvfrom(9000)將返回8000。如果某個IP分片丟失,udp里有個CRC檢驗,如果包不完整就會丟棄,也不會通知是否接收成功,所以UDP是不可靠的傳輸協議,那麼recvfrom(9000)將阻塞。

3.3 UDP丟包問題

在不考慮UDP下層IP層的分片丟失,CRC檢驗包不完整的情況下,造成UDP丟包的因素有哪些呢?

[1] UDP socket緩衝區滿造成的UDP丟包

通過 cat /proc/sys/net/core/rmem_default 和cat /proc/sys/net/core/rmem_max可以查看socket緩衝區的預設值和最大值。如果socket緩衝區滿了,應用程序沒來得及處理在緩衝區中的UDP包,那麼後續來的UDP包會被內核丟棄,造成丟包。在socket緩衝區滿造成丟包的情況下,可以通過增大緩衝區的方法來緩解UDP丟包問題。但是,如果服務已經過載了,簡單的增大緩衝區並不能解決問題,反而會造成滾雪球效應,造成請求全部超時,服務不可用。

[2] UDP socket緩衝區過小造成的UDP丟包

如果Client發送的UDP報文很大,而socket緩衝區過小無法容下該UDP報文,那麼該報文就會丟失。

[3] ARP緩存過期導致UDP丟包

ARP 的緩存時間約10分鐘,APR 緩存列表沒有對方的 MAC 地址或緩存過期的時候,會發送 ARP 請求獲取 MAC 地址,在沒有獲取到 MAC 地址之前,用戶發送出去的 UDP 數據包會被內核緩存到 arp_queue 這個隊列中,默認最多緩存3個包,多餘的 UDP 包會被丟棄。被丟棄的 UDP 包可以從 /proc/net/stat/arp_cache 的最後一列的 unresolved_discards 看到。當然我們可以通過 echo 30 > /proc/sys/net/ipv4/neigh/eth1/unres_qlen 來增大可以緩存的 UDP 包。

UDP 的丟包信息可以從 cat /proc/net/udp 的最後一列drops中得到,而倒數第四列 inode 是丟失 UDP 數據包的 socket 的全局唯一的虛擬i節點號,可以通過這個 inode 號結合 lsof ( lsof -P -n | grep 25445445)來查到具體的進程。

3.4 UDP冗餘傳輸

在外網通信鏈路不穩定的情況下,有什麼辦法可以降低UDP的丟包率呢?一個簡單的辦法來採用冗餘傳輸的方式。如下圖,一般採用較多的是延時雙發,雙髮指的是將原本單發的前後連續的兩個包合併成一個大包發送,這樣發送的數據量是原來的兩倍。這種方式提高丟包率的原理比較簡單,例如本例的冗餘發包方式,在偶數包全丟的情況下,依然能夠還原出完整的數據,也就是在這種情況下,50%的丟包率,依然能夠達到100%的數據接收。

4 UDP真的比TCP要高效嗎

相信很多同學都認為UDP無連接,無需重傳和處理確認,UDP比較高效。然而UDP在大多情況下並不一定比TCP高效,TCP發展至今天,為了適應各種複雜的網路環境,其演算法已經非常豐富,協議本身經過了很多優化,如果能夠合理配置TCP的各種參數選項,那麼在多數的網路環境下TCP是要比UDP更高效的。

4.1 影響UDP高效因素

(1) 無法智能利用空閑帶寬導致資源利用率低

一個簡單的事實是UDP並不會受到MTU的影響,MTU只會影響下層的IP分片,對此UDP一無所知。在極端情況下,UDP每次都是發小包,包是MTU的幾百分之一,這樣就造成UDP包的有效數據佔比較小(UDP頭的封裝成本);或者,UDP每次都是發巨大的UDP包,包大小MTU的幾百倍,這樣會造成下層IP層的大量分片,大量分片的情況下,其中某個分片丟失了,就會導致整個UDP包的無效。由於網路情況是動態變化的,UDP無法根據變化進行調整,發包過大或過小,從而導致帶寬利用率低下,有效吞吐量較低。而TCP有一套智能演算法,當發現數據必須積攢的時候,就說明此時不積攢也不行,TCP的複雜演算法會在延遲和吞吐量之間達到一個很好的平衡。

(2) 無法動態調整發包

由於UDP沒有確認機制,沒有流量控制和擁塞控制,這樣在網路出現擁塞或通信兩端處理能力不匹配的時候,UDP並不會進行調整發送速率,從而導致大量丟包。在丟包的時候,不合理的簡單重傳策略會導致重傳風暴,進一步加劇網路的擁塞,從而導致丟包率雪上加霜。更加嚴重的是,UDP的無秩序性和自私性,一個瘋狂的UDP程序可能會導致這個網路的擁塞,擠壓其他程序的流量帶寬,導致所有業務質量都下降。

(3) 改進UDP的成本較高

可能有同學想到針對UDP的一些缺點,在用戶態做些調整改進,添加上簡單的重傳和動態發包大小優化。然而,這樣的改進並比簡單的,UDP編程可是比TCP要難不少的,考慮到改造成本,為什麼不直接用TCP呢?當然可以拿開源的一些實現來抄一下(例如:libjingle),或者擁抱一下Google的QUIC協議,然而,這些都需要不少成本的。

上面說了這麼多,難道真的不該用UDP了嗎?其實也不是的,在某些場景下,我們還是必須UDP才行的。那麼UDP的較為合適的使用場景是哪些呢?

5 UDP的使用場合

5.1 通信實時性和持續性

在分組交換通信當中,協議棧的成本主要表現在以下兩方面:

[1] 封裝帶來的空間複雜度;

[2] 緩存帶來的時間複雜度。

以上兩者是對立影響的,如果想減少封裝消耗,那麼就必須緩存用戶數據到一定量在一次性封裝發送出去,這樣每個協議包的有效載荷將達到最大化,這無疑是節省了帶寬空間,帶寬利用率較高,但是延時增大了。如果想降低延時,那麼就需要將用戶數據立馬封裝發出去,這樣顯然會造成消耗更多的協議頭等消耗,浪費帶寬空間。

因此,我們進行協議選擇的時候,需要重點考慮一下空間複雜度和時間複雜度間的平衡。通信的持續性對兩者的影響比較大,根據通信的持續性有兩種通信類型:[1] 短連接通信 [2] 長連接通信。對於短連接通信,一方面如果業務只需要發一兩個包並且對丟包有一定的容忍度,同時業務自己有簡單的輪詢或重複機制,那麼採用UDP會較為好些。在這樣的場景下,如果用TCP,僅僅握手就需要3個包,這樣顯然有點不划算,一個典型的例子是DNS查詢。另一方面,如果業務實時性要求非常高,並且不能忍受重傳,那麼首先就是UDP了或者只能用UDP了,例如NTP 協議,重傳NTP消息純屬添亂(為什麼呢?重傳一個過期的時間包過來,還不如發一個新的UDP包同步新的時間過來)。如果NTP協議採用TCP,撇開握手消耗較多數據包交互的問題,由於TCP受Nagel演算法等影響,用戶數據會在一定情況下會被內核緩存延後發送出去,這樣時間同步就會出現比較大的偏差,協議將不可用。

5.2 多點通信

對於一些多點通信的場景,如果採用有連接的TCP,那麼就需要和多個通信節點建立其雙向連接,然後有時在NAT環境下,兩個通信節點建立其直接的TCP連接不是一個容易的事情,在涉及NAT穿越的時候,UDP協議的無連接性使得穿透成功率更高(原因詳見:由於UDP的無連接性,那麼其完全可以向一個組播地址發送數據或者輪轉地向多個目的地持續發送相同的數據,從而更為容易實現多點通信。)

一個典型的場景是多人實時音視頻通信,這種場景下實時性要求比較高,可以容忍一定的丟包率。比如:對於音頻,對端連續發送p1、p2、p3三個包,另一端收到了p1和p3,在沒收到p2的保持p1的最後一個音(也是為什麼有時候網路丟包就會聽到嗞嗞嗞嗞嗞嗞…或者卟卟卟卟卟卟卟卟…重音的原因),等到到p3就接著播p3了,不需要也不能補幀,一補就越來越大的延時。對於這樣的場景就比較合適用UDP了,如果採用TCP,那麼在出現丟包的時候,就可能會出現比較大的延時。

5.3 UDP使用原則

通常情況下,UDP的使用範圍是較小的,在以下的場景下,使用UDP才是明智的。

[1] 實時性要求很高,並且幾乎不能容忍重傳;

例子:NTP協議,實時音視頻通信,多人動作類遊戲中人物動作、位置。

[2] TCP實在不方便實現多點傳輸的情況;

[3] 需要進行NAT穿越;

[4] 對網路狀態很熟悉,確保udp網路中沒有氓流行為,瘋狂搶帶寬;

[5] 熟悉UDP編程。

參考資料

blog.csdn.net/dog250/ar

推薦閱讀:

物聯網技術中的「兩域、六層」
量子計算入門讀物
用CDH5搭建hadoop集群
木犀互聯網技術周刊(第三十三期)
linux忘記root密碼怎麼辦?

TAG:科技 | UDP | 計算機科學 |