CocoaAsyncSocket UDP收發(fā)數(shù)據(jù)包大小限制

之前因為要解決項目的IPv6問題,去CocoaAsyncSocket逛了一下,看到一個比較有意思的issue —— GCDAsyncUDPSocket can not send data when data is greater than 9K? #535。問題很簡單,使用UDP傳輸圖片,可是當圖片大小超過9K的時候無法發(fā)送。

開始我想的很簡單。因為MTU的限制,所以9K大小的數(shù)據(jù)報肯定被分片了。UDP本身是不可靠的傳輸,任何一片數(shù)據(jù)的遺失都會丟棄該數(shù)據(jù)包。一般來說,MTU的大小是1500,那么9K大概分為6-7個包。所以還是有很大可能是數(shù)據(jù)報丟失的問題的。


MTU

但是這個哥們回復我了,他說查了一些資料,在stackoverflow上找到了一篇回答。set max packet size for GCDAsyncUdpSocket。這篇回答并沒有提到iPhone只是說了在Mac上的操作,通過終端命令修改mac的最大緩沖區(qū)大小。他修改以后在模擬器上可以收發(fā)了,但是iPhone上仍然不知道怎么辦。

為此我在源碼里搜了一下關(guān)鍵字max,確實搜到了一個地方提到了maxSize這樣一個東西。

max4ReceiveSize = 9216;
max6ReceiveSize = 9216;

相關(guān)的issue是[CRITICAL] Don't trust GCD to give accurate UDP packet sizes.#222。作者認為dispatch_source_get_data()返回的數(shù)據(jù)是不可靠的,如果數(shù)據(jù)過大它會默不作聲的對你的數(shù)據(jù)做一個截斷,大小是9216。所以他pr上去我發(fā)現(xiàn)的maxSize那一段的代碼。我去Apple那里查了一下dispatch_source_get_data()的文檔,人沒說有這么一茬啊。

我自己做了一個簡單的測試。發(fā)送一段小于9216的數(shù)據(jù),沒有問題正常發(fā)送;如果數(shù)據(jù)超過9216,我用wireshark抓包發(fā)現(xiàn)是沒有UDP的包發(fā)出的。(后來發(fā)現(xiàn)拋出了Message too large的錯誤)。

圖文無關(guān)

對比stackoverflow的回答,我覺得問題應該就是出在iPhone的緩沖區(qū)大小的設置了。一般來說UDP的最大數(shù)據(jù)報大小是65535(IPv4環(huán)境下,因為在UDP數(shù)據(jù)包的首部里,使用16bit的字節(jié)標示數(shù)據(jù)報的長度。所以最大長度就是2^16 - 1 = 65535),但是因為iPhone設置了收發(fā)緩沖區(qū)的大小9216,導致數(shù)據(jù)收發(fā)出現(xiàn)問題了。(發(fā)送數(shù)據(jù)包太大就是Message too large,接受數(shù)據(jù)包太大就會對數(shù)據(jù)截斷)。

我嘗試找了很多地方,google和stackoverflow,去找設置的代碼,在找到結(jié)果之前我還嘗試聯(lián)系了Apple的工程師。在他們回復我之前,我終于找到了解決方案

        /**
         * The theoretical maximum size of any IPv4 UDP packet is UINT16_MAX = 65535.
         * The theoretical maximum size of any IPv6 UDP packet is UINT32_MAX = 4294967295.
         *
         * The default maximum size of the UDP buffer in iOS is 9216 bytes.
         *
         * This is the reason of #222(GCD does not necessarily return the size of an entire UDP packet) and
         *  #535(GCDAsyncUDPSocket can not send data when data is greater than 9K)
         *
         *
         * Enlarge the maximum size of UDP packet.
         * I can not ensure the protocol type now so that the max size is set to 65535 :)
         **/
        int maximumBufferSize = 65535;
        
        status = setsockopt(socketFD, SOL_SOCKET, SO_SNDBUF, (const char*)&maximumBufferSize, sizeof(int));
        if (status == -1)
        {
            if (errPtr)
                *errPtr = [self errnoErrorWithReason:@"Error setting send buffer size (setsockopt)"];
            close(socketFD);
            return SOCKET_NULL;
        }
        
        status = setsockopt(socketFD, SOL_SOCKET, SO_RCVBUF, (const char*)&maximumBufferSize, sizeof(int));
        if (status == -1)
        {
            if (errPtr)
                *errPtr = [self errnoErrorWithReason:@"Error setting receive buffer size (setsockopt)"];
            close(socketFD);
            return SOCKET_NULL;
        }

在1988行處添加這段代碼即可。

在后來的pr里,我參考了max4ReceiveSize = 9216;max6ReceiveSize = 9216;的方案,添加了maxSendSize屬性來允許使用者自行設定最大發(fā)送數(shù)據(jù)包大小。(因為IPv4和IPv6對于UDP數(shù)據(jù)包的最大大小是不同的,但是創(chuàng)建socket之前我們無法判斷當前的網(wǎng)絡環(huán)境,所以按照IPv4的標準設置)之前他們對dispatch_source_get_data()的誤解我也沒沒有修改他們的代碼了。之前你即使設置了```
max4ReceiveSize = 9216;max6ReceiveSize = 9216;
``的大小超過9216,還是只能收到9216,添加了上面的代碼以后這個屬性才算是真正的最大接受數(shù)據(jù)包大小了。

但是之后我得到了Apple工程師的回復:

You’re approach this the wrong way. Given that a typical link MTU is 1500 bytes, a large UDP datagram will have to be fragmented, and that’s both expensive and risky (if one fragment goes missing, the entire datagram is lost). You are much better off sending a large number of smaller UDP datagrams, preferably using a path MTU algorithm to avoid fragmentation.

其實這也是我之前想說的。雖然我們修改了緩存區(qū)的大小,但是UDP本身作為一個不可靠的傳輸,分片后的數(shù)據(jù)很容易因為其中一片的遺失而全部丟棄。雖然從及時性的考慮上很多時候UDP確實是一個比TCP好的選擇,但是過大的數(shù)據(jù)選擇UDP還不是一個很好的選擇。分包太多太容易丟失了。我想這大概是默認最大收發(fā)數(shù)據(jù)大小是9216而不是65535的原因。

這段話我寫入了注釋,在幫忙寫完了和我添加的代碼相關(guān)的單元測試以后,pr終于被merge進主支了。

問題告一段落~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,238評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,430評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,134評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,893評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,653評論 6 408
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,136評論 1 323
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,212評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,372評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,888評論 1 334
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,738評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,939評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,482評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,179評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,588評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,829評論 1 283
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,610評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,916評論 2 372

推薦閱讀更多精彩內(nèi)容