1. 程式人生 > >關於UDP-讀這篇就夠了(疑難雜症和使用)

關於UDP-讀這篇就夠了(疑難雜症和使用)

關於UDP-讀這篇就夠了(疑難雜症和使用)

本文為轉載文章

原文連結:https://www.qcloud.com/community/article/848077001486437077

版權歸原文所有

 

 

關於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程式設計