CocoaAsyncSocket UDP收发数据包大小限制

之前因为要解决项目的IPv6问题,去CocoaAsyncSocket逛了一下,看到一个比较有意思的issue —— GCDAsyncUDPSocket can not send data when data is greater than 9K? #535。问题很简单,使用UDP传输图片,可是当图片大小超过9K的时候无法发送。

开始我想的很简单。因为MTU的限制,所以9K大小的数据报肯定被分片了。UDP本身是不可靠的传输,任何一片数据的遗失都会丢弃该数据包。一般来说,MTU的大小是1500,那么9K大概分为6-7个包。所以还是有很大可能是数据报丢失的问题的。


MTU

但是这个哥们回复我了,他说查了一些资料,在stackoverflow上找到了一篇回答。set max packet size for GCDAsyncUdpSocket。这篇回答并没有提到iPhone只是说了在Mac上的操作,通过终端命令修改mac的最大缓冲区大小。他修改以后在模拟器上可以收发了,但是iPhone上仍然不知道怎么办。

为此我在源码里搜了一下关键字max,确实搜到了一个地方提到了maxSize这样一个东西。

max4ReceiveSize = 9216;
max6ReceiveSize = 9216;

相关的issue是[CRITICAL] Don't trust GCD to give accurate UDP packet sizes.#222。作者认为dispatch_source_get_data()返回的数据是不可靠的,如果数据过大它会默不作声的对你的数据做一个截断,大小是9216。所以他pr上去我发现的maxSize那一段的代码。我去Apple那里查了一下dispatch_source_get_data()的文档,人没说有这么一茬啊。

我自己做了一个简单的测试。发送一段小于9216的数据,没有问题正常发送;如果数据超过9216,我用wireshark抓包发现是没有UDP的包发出的。(后来发现抛出了Message too large的错误)。

图文无关

对比stackoverflow的回答,我觉得问题应该就是出在iPhone的缓冲区大小的设置了。一般来说UDP的最大数据报大小是65535(IPv4环境下,因为在UDP数据包的首部里,使用16bit的字节标示数据报的长度。所以最大长度就是2^16 - 1 = 65535),但是因为iPhone设置了收发缓冲区的大小9216,导致数据收发出现问题了。(发送数据包太大就是Message too large,接受数据包太大就会对数据截断)。

我尝试找了很多地方,google和stackoverflow,去找设置的代码,在找到结果之前我还尝试联系了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属性来允许使用者自行设定最大发送数据包大小。(因为IPv4和IPv6对于UDP数据包的最大大小是不同的,但是创建socket之前我们无法判断当前的网络环境,所以按照IPv4的标准设置)之前他们对dispatch_source_get_data()的误解我也没没有修改他们的代码了。之前你即使设置了```
max4ReceiveSize = 9216;max6ReceiveSize = 9216;
``的大小超过9216,还是只能收到9216,添加了上面的代码以后这个属性才算是真正的最大接受数据包大小了。

但是之后我得到了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.

其实这也是我之前想说的。虽然我们修改了缓存区的大小,但是UDP本身作为一个不可靠的传输,分片后的数据很容易因为其中一片的遗失而全部丢弃。虽然从及时性的考虑上很多时候UDP确实是一个比TCP好的选择,但是过大的数据选择UDP还不是一个很好的选择。分包太多太容易丢失了。我想这大概是默认最大收发数据大小是9216而不是65535的原因。

这段话我写入了注释,在帮忙写完了和我添加的代码相关的单元测试以后,pr终于被merge进主支了。

问题告一段落~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,711评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,932评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,770评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,799评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,697评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,069评论 1 276
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,535评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,200评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,353评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,290评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,331评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,020评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,610评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,694评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,927评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,330评论 2 346
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,904评论 2 341

推荐阅读更多精彩内容