1. 程式人生 > >Linux網路程式設計-UDP接收資料丟包解決方案

Linux網路程式設計-UDP接收資料丟包解決方案

序言

專案涉及基於UDP的socket通訊,該部分的基本情況如下:

  • 發端程式:主函式開啟4個發包執行緒,每個執行緒傳送一定量的資料,通過限制發包速率限制發包流量。
  • 收端程式:主函式對應開啟4個收包執行緒,每個執行緒收取對應埠的資料,收到資料包即時封裝處理。
  • 其他說明

    • 本地收發。如果不限制發包速率將會非常快

    • 基於UDP。使用recvfrom()函式收包

    • recvfrom()接收後立即將包加入佇列並封裝處理,即一次處理單個包

    • 發包流量最大160Mbps = 20MBps

    • 多種流量:160Mbps,80Mbps,40Mbps等進行測試

  • 測試方式
    • 傳送一定時間的資料。
      • 如設定傳送1s,傳送資料量160Mbits,即160Mbps流量
    • 收到資料包後進行包計數。
      • 統計收包率/丟包率
    • 記錄收端處理資料耗時,對比發端耗時。
      • 即測試收端處理速度,不過應該注意此處的耗時只是處理所收到資料包的耗時,並不一定是所有資料量的耗時,如果存在丟包的話。
  • 但是現在測試出現的問題如下

    • 收包率低/丟包率高。

      • 丟包率最高時可達67%
        • 速率逐漸降低,收包率有上升趨勢
        • 到一定速率,速率繼續降低,收包數也不繼續上升
    • 最大容量下耗時超過了發包耗時

      • 耗時最大可達傳送時間的2倍以上
        • 速率逐漸降低,耗時有減小趨勢
        • 到一定速率,速率繼續降低,由於只能處理一定收包數,耗時基本穩定

1. 問題分析

  • 收包率低/丟包率高的原因分析

    • (1) 快取太小,不能及時接收資料。

      • 連續多個UDP包超過了UDP接收緩衝區大小
        • 如:UDP包過大
        • 如:UDP發包速率過快,突發大資料流量超過了緩衝區上限
    • (2)recvfrom()接收到資料之後處理速度太慢

      • 如果資料接收和處理/渲染是連續進行的,那麼可能由於資料處理過慢,兩次recvfrom呼叫的時間間隔裡發過來的包丟失

2. 問題驗證和解決措施

  • (1) 快取太小不能及時接收資料

    • [1] 分析:

      • UDP無需真正的傳送緩衝區:

        • UDP是不可靠連線,不必儲存應用程序的資料拷貝,因此無需真正的傳送緩衝區(TCP需要)。應用程序的資料在沿協議棧往下傳遞,以某種形式拷貝到核心緩衝區,然而資料鏈路層在送出資料之後將丟棄該拷貝
      • UDP是沒有流量控制的:

        • 較快的傳送端可以很容易淹沒較慢的接收端,導致接收端的UDP丟棄資料報。
        • UDP套接字的緩衝區是以一個個報文為單位進行排隊的,呼叫一次recvfrom表示提取一個報文,和TCP基於位元組流的方式是不同的
      • 因此,如果socket接收快取設定過小,就會因為UDP包過大或者發包速率過快而丟包

    • [2] 解決方法:重新設定UDP接收緩衝區大小

      • UDP接收緩衝區預設值:cat /proc/sys/net/core/rmem_default
        • 本系統:212992 = 208K
      • UDP接收緩衝區最大值:cat /proc/sys/net/core/rmem_max,UDP最大可設定值的一半
        • 本系統:212992 = 208K,即最大值425984 = 416K
      • UDP接收緩衝區最小值:sysctl -a | grep rmem

        • 本系統:net.ipv4.udp_rmem_min = 4096 = 2K,由核心的巨集決定
      • UDP傳送緩衝區預設值:cat /proc/sys/net/core/wmem_default

        • 本系統:212992 = 208K
      • UDP傳送緩衝區最大值:cat /proc/sys/net/core/wmem_max
        • 本系統:212992 = 208K,即最大值425984 = 416K
      • UDP傳送緩衝區最小值:sysctl -a | grep wmem
        • 本系統:net.ipv4.udp_wmem_min = 4096 = 2K,由核心的巨集決定
    • [3] 解決步驟:

      • 調整UDP緩衝區大小:使用函式setsockopt()函式修改接收緩衝區大小

        • 緩衝區改大可以處理突發的大流量資料,不至於資料(視音訊等)變化、流量突然增大的時候緩衝區溢位
      • 重新設定系統緩衝區最大值,再調整UDP緩衝區大小:

        • 開啟配置檔案:sudo vim /etc/sysctl.conf

        • 在檔案末尾新增:net.core.rmem_max = 6291456

          • 將接收緩衝最大值設定為12582912 = 12M
        • 執行配置:sysctl -p

        • 重新檢視最大值:cat /proc/sys/net/core/rmem_max

        • 發現系統接收緩衝最大值已改變,此時可以通過setsockopt函式設定更大接收快取。傳送緩衝最大值也可以通過類似方式修改:net.core.wmem_max = 6291456,sysctl -p

/* setsockopt()函式修改:*/

//函式原型
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

sockfd: 標識一個套接字的描述字
level: 選項定義的層次:支援SOL_SOCKET, IPPROTO_TCP, IPPROTO_IP,和IPPROTO_IPV6
optname:需設定得選項 SO_RCVBUF(接收緩衝區),SO_SNDBUF(傳送緩衝區)
optval:指標,指向存放選項待設定的新值的緩衝區
optlen:optval的大小


//示例
int recv_size = 2 * 1024 * 1024;    //設定為4M
setsockopt(s,SOL_SOCKET, SO_RCVBUF, (const char *)&recv_size,sizeof(recv_size));  
  • (2)recvfrom()接收到資料之後處理速度太慢

    • [1] 分析:

      • UDP無需真正的緩衝區,不必儲存應用程序的資料拷貝

        • 這就對資料處理的實時性提出了很高要求
      • recvfrom()接收速率並不是系統受限因素

        • recvfrom()處理速度是由CPU速度決定的(看到過這種說法,待驗證),如果執行緒正常呼叫,幾百Mbps的速度該函式肯定能跟上
      • 資料處理是速度受限因素之一

        • 程式在recvfrom接收到資料之後還進行了封裝處理,即資料接收和資料處理是繫結的,處理速度跟不上接收速度(渲染速度跟不上socket接收速度)
      • 執行緒掛起再喚醒耗時受限速度之二

        • 儘管由CPU決定的recvfrom接收速度足夠快,但如果接收資料執行緒從掛起再到喚醒接收資料,這個過程耗時可達數百毫秒
    • [2] 解決方法:

      • 資料處理速度受限:資料接收和資料處理分離
        • 接收資料單獨儲存,然後直接返回,保證recvfrom不丟資料或超時
        • 資料處理由其他函式或執行緒獨立完成
      • 執行緒響應速度受限:保持執行緒一直在執行或監聽狀態
        • 因為資料接收和處理函式都在同一執行緒,可將資料接收和處理從邏輯上分開
        • 如果不在統一執行緒,由於資料到了再起執行緒速度過慢,可考慮使用執行緒池技術
    • [3] 解決步驟:

      • recvfrom和資料處理函式不再順序執行一次只處理一個包。
      • 邏輯上分離資料接收和處理:
        • recvfrom負責接收資料並存儲,設定單獨計數1
        • 資料處理函式負責從從儲存中拿資料並處理,設定單獨計數2
      • 抓包執行緒一直處於執行狀態:while
        • 資料處理可以等待喚醒
        • 但資料接收需要一直進行,否則難免丟包

補充:TCP緩衝區檢視和修改命令

  • TCP接收緩衝區大小

    • TCP接收緩衝區預設值:cat /proc/sys/net/ipv4/tcp_rmem
      • 本系統:87380,約85K
    • TCP接收緩衝區最大值:cat /proc/sys/net/core/rmem_max ,TCP最大可設定值的一半(與UDP同)
      • 本系統:212992 = 208K,即最大值425984 = 416K。
      • 已被我修改為:3145728 = 3M,即最大值6291456 = 6M
    • TCP接收緩衝區最小值:sysctl -a | grep rmem
      • 本系統:net.ipv4.udp_rmem_min = 4096 = 2K
    • 通用命令:cat /proc/sys/net/ipv4/tcp_rmem
      • 4096 87380 6291456,依次為最小值、預設值、最大值
  • TCP接收緩衝區大小

    • TCP傳送緩衝區預設值:cat /proc/sys/net/ipv4/tcp_wmem
      • 本系統:16384 = 16K
    • TCP傳送緩衝區最大值:cat /proc/sys/net/core/wmem_max,TCP最大可設定值的一半
      • 本系統:212992 = 208K,即最大值425984 = 416K
    • TCP傳送緩衝區最小值:sysctl -a | grep wmem
      • 本系統:net.ipv4.udp_wmem_min = 4096 = 2K
    • 通用命令:cat /proc/sys/net/ipv4/tcp_wmem
      • 4096 16384 4194304,依次為最小值、預設值、最大值

    注:通過cat /proc/sys/net/ipv4/tcp_wmem命令和cat /proc/sys/net/core/wmem_max命令得出的TCP傳送緩衝區最大值不一致,我覺得應該以cat /proc/sys/net/ipv4/tcp_wmem為準,即最大值為4M。


Acknowledgements:
http://blog.csdn.net/getnextwindow/article/details/24328117?utm_source=tuicool&utm_medium=referral
http://blog.csdn.net/ljh0302/article/details/49738191
http://blog.csdn.net/herecles/article/details/8146017
http://blog.sina.com.cn/s/blog_858820890100sfpx.html
http://bbs.csdn.net/topics/390958727

2017.07.29
文中錯誤和不足部分,歡迎交流指正