udp 數據包長度,udp 數據包的理論長度
udp 數據包的理論長度是多少,合適的 udp 數據包應該是多少呢?從 tcp-ip 詳解卷一第 11 章的 udp 數據包的包頭可以看出,udp 的最大包長度是 216-1 的個字節。由於 udp 包頭占 8 個字節,而在 ip 層進行封裝後的 ip 包頭占去 20 字節,所以這個是 udp 數據包的最大理論長度是 216-1-8-20=65507。

然而這個只是 udp 數據包的最大理論長度。首先,我們知道,tcp/ip 通常被認為是一個四層協議系統,包括鏈路層、網絡層、運輸層、應用層。udp 屬於運輸層,在傳輸過程中,udp 包的整體是作為下層協議的數據欄位進行傳輸的,它的長度大小受到下層 ip 層和數據鏈路層協議的制約。
mtu 相關概念
乙太網(ethernet)數據幀的長度必須在 46-1500 字節之間,這是由乙太網的物理特性決定的。這個 1500 字節被稱為鏈路層的 mtu(最大傳輸單元)。網際網路協議允許 ip 分片,這樣就可以將數據包分成足夠小的片段以通過那些最大傳輸單元小於該數據包原始大小的鏈路了。這一分片過程發生在網絡層,它使用的是將分組發送到鏈路上的網絡接口的最大傳輸單元的值。這個最大傳輸單元的值就是 mtu(maximum transmission unit)。它是指一種通信協議的某一層上面所能通過的最大數據包大小(以字節為單位)。最大傳輸單元這個參數通常與通信接口有關(網絡接口卡、串口等)。
在網際網路協議中,一條網際網路傳輸路徑的“路徑最大傳輸單元”被定義為從源地址到目的地址所經過“路徑”上的所有 ip 跳的最大傳輸單元的最小值。
需要注意的是,loopback 的 mtu 不受上述限制,查看 loopback mtu 值:

[root@bogon ~]# cat /sys/class/net/lo/mtu
65536
ip 分包 udp 數據包長度的影響
如上所述,由於網絡接口卡的制約,mtu 的長度被限制在 1500 字節,這個長度指的是鏈路層的數據區。對於大於這個數值的分組可能被分片,否則無法發送,而分組交換的網絡是不可靠的,存在著丟包。ip 協議的發送方不做重傳。接收方只有在收到全部的分片後才能 reassemble 並送至上層協議處理代碼,否則在應用程式看來這些分組已經被丟棄。
假定同一時刻網絡丟包的概率是均等的,那麼較大的 ip datagram 必然有更大的概率被丟棄,因為只要丟失了一個 fragment,就導致整個 ip datagram 接收不到。不超過 mtu 的分組是不存在分片問題的。
mtu 的值並不包括鏈路層的首部和尾部的 18 個字節。所以,這個 1500 字節就是網絡層 ip 數據報的長度限制。因為 ip 數據報的首部為 20 字節,所以 ip 數據報的數據區長度最大為 1480 字節。而這個 1480 字節就是用來放 tcp 傳來的 tcp 報文段或 udp 傳來的 udp 數據報的。又因為 udp 數據報的首部 8 字節,所以 udp 數據報的數據區最大長度為 1472 字節。這個 1472 字節就是我們可以使用的字節數。

當我們發送的 udp 數據大於 1472 的時候會怎樣呢?這也就是說 ip 數據報大於 1500 字節,大於 mtu。這個時候發送方 ip 層就需要分片(fragmentation)。把數據報分成若干片,使每一片都小於 mtu。而接收方 ip 層則需要進行數據報的重組。而更嚴重的是,由於 udp 的特性,當某一片數據傳送中丟失時,接收方便無法重組數據報。將導致丟棄整個 udp 數據報。因此,在普通的區域網環境下,將 udp 的數據控制在 1472 字節以下為好。
進行 internet 編程時則不同,因為 internet 上的路由器可能會將 mtu 設為不同的值。如果我們假定 mtu 為 1500 來發送數據的,而途經的某個網絡的 mtu 值小於 1500 字節,那麼系統將會使用一系列的機制來調整 mtu 值,使數據報能夠順利到達目的地。鑑於 internet 上的標準 mtu 值為 576 字節,所以在進行 internet 的 udp 編程時,最好將 udp 的數據長度控制項在 548 字節(576-8-20)以內。
udp 丟包
udp 丟包是指網卡接收到數據包後,linux 內核的 tcp/ip 協議棧在 udp 數據包處理過程中的丟包,主要原因有兩個:
1、udp 數據包格式錯誤或校驗和檢查失敗。
2、應用程式來不及處理 udp 數據包。
對於原因 1,udp 數據包本身的錯誤很少見,應用程式也不可控,本文不討論。
首先居間通用的 udp 丟包檢測方法,使用 netstat 命令,加-su 參數。
# netstat -su
Udp:
2495354 packets received
2100876 packets to unknown port received.
3596307 packet receive errors
14412863 packets sent
RcvbufErrors: 3596307
SndbufErrors: 0
從上面的輸出中,可以看到有一行輸出包含了"packet receive errors",如果每隔一段時間執行 netstat -su,發現行首的數字不斷變大,表明發生了 udp 丟包。
下面居間一下應用程式來不及處理而導致 udp 丟包的常見原因:
1、linux内核socket缓冲区设的太小 # cat /proc/sys/net/core/rmem_default
# cat /proc/sys/net/core/rmem_max
可以查看 socket 緩衝區的預設值和最大值。
rmem_default 和 rmem_max 設置為多大合適呢?如果伺服器的性能壓力不大,對處理時延也沒有很嚴格的要求,設置為 1m 左右即可。如果伺服器的性能壓力較大,或者對處理時延有很嚴格的要求,則必須謹慎設置 rmem_default 和 rmem_max,如果設得過小,會導致丟包,如果設得過大,會出現滾雪球。
2、伺服器負載過高,占用了大量 cpu 資源,無法及時處理 linux 內核 socket 緩衝區中的 udp 數據包,導致丟包。
一般來說,伺服器負載過高有兩個原因:收到的 udp 包過多;伺服器進程存在性能瓶頸。如果收到的 udp 包過多,就要考慮擴容了。伺服器進程存在性能瓶頸屬於性能優化的範疇,這裡不作過多討論。
3、磁碟 io 忙
伺服器有大量 io 操作,會導致進程阻塞,cpu 都在等待磁碟 io,不能及時處理內核 socket 緩衝區中的 udp 數據包。如果業務本身就是 io 密集型的,要考慮在架構上進行優化,合理使用緩存降低磁碟 io。
這裡有一個容易忽視的問題:很多伺服器都有在本地磁碟記錄日誌的功能,由於運維誤操作導致日誌記錄的級別過高,或者某些錯誤突然大量出現,使得往磁碟寫日誌的 io 請求量很大,磁碟 io 忙,導致 udp 丟包。
對於運維誤操作,可以加強運營環境的管理,防止出錯。如果業務確實需要記錄大量的日誌,可以使用內存 log 或者遠程 log。
4、物理內存不夠用,出現 swap 交換
swap 交換本質上也是一種磁碟 io 忙,因為比較特殊,容易被忽視,所以單列出來。
只要規劃好物理內存的使用,並且合理設置系統參數,可以避免這個問題。
5)磁碟滿導致無法 io
沒有規劃好磁碟的使用,監控不到位,導致磁碟被寫滿後伺服器進程無法 io,處於阻塞狀態。最根本的辦法是規劃好磁碟的使用,防止業務數據或日誌文件把磁碟塞滿,同時加強監控,例如開發一個通用的工具,當磁碟使用率達到 80%時就持續告警,留出充足的反應時間。
udp 收包能力測試
測試環境
處理器:intel(r) xeon(r) cpu x3440@2.53ghz,4 核,8 超線程,千兆乙太網卡,8g 內存
模型 1
單機,單線程異步 udp 服務,無業務邏輯,只有收包操作,除 udp 包頭外,一個字節數據。
測試結果



現象:
1、單機 udp 收包處理能力可以每秒達到 150w 左右。
2、處理能力隨著進程個數的增加而增強。
3、在處理達到峰值時,cpu 資源並未耗盡。
結論:
1、udp 的處理能力還是非常可觀的。
2、對於現象 2 和現象 3,可以看出,性能的瓶頸在網卡,而不在 cpu,cpu 的增加,處理能力的上升,來源於丟包(udp_error)個數的減少。
模型 2
其他測試條件同模型 1,除 udp 包頭外,一百個字節數據。
測試結果



現象:
1、100 個字節的包大小,比較符合平常的業務情形。
2、udp 的處理能力還是非常可觀,單機峰值可以到達每秒 75w。
3、在 4,8 個進程時,沒有記錄 cpu 的占用情況(網卡流量耗盡),不過可以肯定的是,cpu 未耗盡。
4、隨著進程個數的上升,處理能力沒有明顯提升,但是,丟包(udp_error)的個數大幅下降。
模型 3
單機,單進程,多線程異步 udp 服務,多線程共用一個 fd,無業務邏輯,除 udp 包頭外,一個字節數據。
測試結果:

現象:
1、隨著線程個數的增加,處理能力不升反降。
結論:
1、多線程共用一個 fd,會造成相當大的鎖爭用。
2、多線程共用一個 fd,當有包來時,會激活所有的線程,導致頻繁的上下文切換。
最終結論:
1、udp 處理能力非常可觀,在日常的業務情形中,udp 一般不會成為性能瓶頸。
2、隨著進程個數的增加,處理能力未明顯上升,但是丟包個數明顯下降。
3、本次測試過程中,瓶頸在網卡,而不在 cpu。
4、採用多進程監聽不同埠的模型,而不是多進程或多線程監聽同一個埠。
總結
