1,udp丟包linux
困擾幾天的udp內網傳輸部分終於作通了,解決的關鍵就在於setsockopt的調用,設置接收緩衝。windows
遇到的問題是這樣的,主機端發送udp數據包:服務器
應用層的包大小爲1452byte大小,這樣拆包是根據以太網的MTU爲1500字節而考慮的(固然外網狀態下並不必定就是以太網網絡,路由MTU可能更加小),由於在網絡層和傳輸層還有8byte的udp包頭和20byte的ip包頭,因此以太網幀大小爲1452+8+20 = 1480byte。網絡
主機端(linux)如今接了11路視頻數據,發送的數據量仍是很大的,但通過測試,數據是能夠發送出去的,發送端沒有問題。我在客戶端用一個線程專門接包,而後進行處理,可老是處理不過來,當鏈接路數比較多的時候,即碼流增大時,出現接收不過來的狀況。開始覺得是主機端問題,進行寫文件測試發現主機端徹底能夠承受11路數據的發送(udp數據包),而接收端對每一個部分都進行了詳細測試,都沒有效率問題而影響接包的處理,最後將目光放在了接收緩衝的問題上,通過查證,windows程序默認的udp socket的接收和發送緩衝都是8kB, 而將接收緩衝調大後,立刻解決了丟包現象: socket
int n = 512*1024; setsockopt(m_hRcvSock, SOL_SOCKET, SO_RCVBUF, (const char*)&n, sizeof(n));tcp
可見對於通常而言,8kB是足夠了,可是對於要接收大量數據時,默認的接收緩衝(udp)是不夠的。須要進行手工設置,不然會形成包的丟失從而數據錯誤。之因此一開始沒有想到這上面是由於咱們原來的網絡是用tcp進行的視頻傳輸(暫時沒有對tcp的接收和發送緩衝進行查證),而tcp狀態下咱們能夠很好的在內網傳輸16路的實時視頻數據,故覺得在發送和接收上udp和tcp同樣不存在瓶頸問題。後查得tcp是會進行流量控制的,下面是一段摘抄:函數
說到流量控制,不得不提到TCP的另外一個重要概念-—窗口。窗口表示了接收主機能接收的最大數據量,而且,窗口大小是隨着主機資源和主機當前正在接收多少個傳輸數量而變化的。主機將窗口字段用於流量控制,也就是說,流量控制是TCP窗口的一個功能。TCP採用流量控制管理進入接收主機緩衝區的數據流量。若是發送主機傳輸數據的速度比接收主機處理數據的速度更快以致接收主機緩衝區已滿不能處理更多的數據時,則接收主機就會請求發送主機下降數據發送速度直到接收主機能夠接收更多的數據爲止;相反,若是接收主機可以處理更多的數據,則會請求發送主機加快數據的發送速度,這就是流量控制的用途,它保證了數據在傳輸的過程當中完整的傳送到接收主機。測試
能夠看出因爲tcp是基於鏈接的,因此其在傳輸過程當中會犧牲不少來進行傳輸的保證,故即便速度降低也會保證接收端的有序和正確接收。而udp是非鏈接的,其發送後不進行任何處理在保證數據的傳輸,高效但無保障,一切檢驗和有序以及完整處理均須要應用層來完成(這讓人想起了RTCP)。大數據
2,綁定失敗spa
還有一個setsockopt的選相是SO_REUSEADDR, 今天在綁定一個地址來進行偵聽的時候,處理上是每來一個鏈接,便偵聽其地址發送來的udp端口,因爲要屢次綁定,而開始時總遇到地址重複的錯誤,後來一查發現,同一個地址進行端口綁定,好像有一個時間間隔限制,該限制之內不能重複綁定,故我進行了以上的設置,就能夠屢次重複綁定了~
通過查證,我遇到的是地址使用錯誤的問題:
使用 bind API 函數來綁定一個地址(一個接口和一個端口)到一個套接字端點。能夠在服務器設置中使用這個函數,以便限制可能有鏈接到來的接口。也能夠在客戶端設置中使用這個函數,以便限制應當供出去的鏈接所使用的接口。bind最多見的用法是關聯端口號和服務器,並使用通配符地址(INADDR_ANY),它容許任何接口爲到來的鏈接所使用。bind 廣泛遭遇的問題是試圖綁定一個已經在使用的端口。該陷阱是也許沒有活動的套接字存在,但仍然禁止綁定端口(bind 返回EADDRINUSE),它由 TCP 套接字狀態 TIME_WAIT 引發。該狀態在套接字關閉後約保留 2 到 4 分鐘。在 TIME_WAIT 狀態退出以後,套接字被刪除,該地址才能被從新綁定而不出問題。
等待 TIME_WAIT 結束多是使人惱火的一件事,特別是若是您正在開發一個套接字服務器,就須要中止服務器來作一些改動,而後重啓。幸運的是,有方法能夠避開 TIME_WAIT 狀態。能夠給套接字應用 SO_REUSEADDR 套接字選項,以便端口能夠立刻重用。
一樣,我在每次UDP偵聽socket使用完畢後,使用closesocket將使用的socket清除,這樣手動釋放後,不須要再進行setsockopt的設置也能夠重複綁定了。今後可看出,本來我建立的socket爲局部的,但其釋放好像並不一樣與普通的c變量的釋放方式,故在函數下次調用時候,出現地址重複的錯誤,而手動清除是保險的。該錯誤在linux和windows平臺均有,但好像windows再調用setsockopt後,socket接收有異常,偶爾接收不了UDP報文,而linux下沒有此種現象。
什麼會致使udp丟包呢,我這裏列舉了以下幾點緣由:
1.調用recv方法接收端收到數據後,處理數據花了一些時間,處理完後再次調用recv方法,在這二次調用間隔裏,發過來的包可能丟失。對於這種狀況能夠修改接收端,將包接收後存入一個緩衝區,而後迅速返回繼續recv。
2.發送的包巨大丟包。雖然send方法會幫你作大包切割成小包發送的事情,但包太大也不行。例如超過30K的一個udp包,不切割直接經過send方法發送也會致使這個包丟失。這種狀況須要切割成小包再逐個send。
3.發送的包較大,超過mtu size數倍,幾個大的udp包可能會超過接收者的緩衝,致使丟包。這種狀況能夠設置socket接收緩衝。之前遇到過這種問題,我把接收緩衝設置成64K就解決了。
int nRecvBuf=32*1024;//設置爲32K
setsockopt(s,SOL_SOCKET,SO_RCVBUF,(const char*)&nRecvBuf,sizeof(int));
4.發送的包頻率太快,雖然每一個包的大小都小於mtu size 可是頻率太快,例如40多個mut size的包連續發送中間不sleep,也有可能致使丟包。這種狀況也有時能夠經過設置socket接收緩衝解決,但有時解決不了。
5.發送的廣播包或組播包在windws和linux下都接收正常,而arm上接收出現丟包。這個還很差解決,個人解決方法是大包切割成大小爲1448的小包發送,每一個包之間sleep 1毫秒,雖然笨,但有效。我這裏mtu size爲1500字節,減去udp包頭8個字節,減去傳輸層幾十個字節,實際數據位1448字節。
除此以外還能夠試試設置arm操做系統緩衝:
//設置mtu size 1500最大
ifconfig eth0 mtu 1500
//查看接收緩衝最大和默認大小。
sysctl -A | grep rmem
//設置接收緩衝的最大大小
sysctl -w net.core.rmem_max=1048576
sysctl -w net.core.rmem_default=1048576
sysctl -w net.ipv4.udp_mem=1048576
sysctl -w net.ipv4.udp_rmem_min=1048576
6,局域網內不丟包,公網上丟包。這個問題我也是經過切割小包並sleep發送解決的。若是流量太大,這個辦法也不靈了。
總之udp丟包老是會有的,若是出現了用個人方法解決不了,還有這個幾個方法: 要麼減少流量,要麼換tcp協議傳輸,要麼作丟包重傳的工做