CocoaAsyncSocket UDP发送数据超过包大小限制(Message too long)

最近在做iOS上,基于UDP传输音视频时遇到的一个问题,这边纪录一下:

由于考虑实时性比较高,所以采用了 CocoaAsyncSocket 的UDP框架来实现,将视频切割成一帧帧的图片发给服务端,不过,在发送图片的过程中,发现:

当图片大于9k大小时,会发送失败;

在didclose代理方法里,会打印错误信息:Message too long

 func udpSocketDidClose(_ sock: GCDAsyncUdpSocket, withError error: Error?) {
        print("udp close:(error?.localizedDescription)")
    }

而且senddata成功或失败的都跳过了,没有执行

    func udpSocket(_ sock: GCDAsyncUdpSocket, didSendDataWithTag tag: Int) {
        print("发送信息成功")
    }

其实就是数据太长,导致socket直接关闭了。。。

查了好些资料,发现在OS X上,由于是因为:默认情况下,OSX具有有限的最大是9216个字节的UDP包。

这样就阻止了超过大小的包的发送。

然后,有一种办法,是通过终端让系统增大限制数;

sudo sysctl -w net.inet.udp.maxdgram=65507

这样执行完,在模拟器上运行,的确是可以实现超过9k的图片的发送,不过在真机上,就没办法了。。。

如果想查看udp其他信息,这样:

sudo sysctl -w net.inet.udp

不过,这种办法,并不是最终的解决办法,所以不知道还有没有更好的办法呢。。。

参考资料:

1、UDP Message too long

2、set max packet size for GCDAsyncUdpSocket

3、GCDAsyncUDPSocket can not send data when data is greater than 9K?

=======================!!!!!!!解决了!!!!!!!!!!!!=======================

感谢github的大神 Noskthing 的帮助

参考:

https://github.com/robbiehanson/CocoaAsyncSocket/issues/535

https://github.com/robbiehanson/CocoaAsyncSocket/pull/536

方法:

修改GCDAsyncUdpSocket.m内文件,添加一段话

 /**
         * 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行左右开始

上面的这段代码就是从源码上修改缓存池最大限制,使能够传输超过9216的data,经过我的测试,在不超过64k的前提下,都是可以发送的。 

===========================update 2017.3.24  分片传输data =================

另外我写了个demo,如果想用分片,也是可以解决的

思路大概这样:

发送端:

1、对要发送的图片先处理:大于9000的分片多次发送

2、每次发送的片带上一个头,三部分组成:分片标示符、页码数据、补充数据,且限定10位

    a:分片标示符表示这段数据是分片,需要分片处理,比如用字符“flag”(后来我发现不加标识符好像也可以,看个人了)

    b:页码数据包含当前页和总页,两者用字符-分割(分割是为了接收方法里截取),总页相当于索引总页,比如有3段,就是0、1、2中的2,可以用余数理解。

    c:如果前两段不满10位,不足用字符a补齐(a是举例,可以自行更换)

这样比如:一张图片有20k,会分成3段发送,如下:

flag0-2aaa+分片数据

flag1-2aaa+分片数据

flag2-2aaa+分片数据   

解释:构成一个头,然后再拼接上真实的图片数据,两部分组合进行发送。

接收端:

1、先定义一过全局可变data类型属性(NSMutableData),用于封装一段段分片,比如:

var showData:NSMutableData! = nil

2、每次在didReceive里,先根据data和showData判断是否是分片数据

3、如果不是分片,直接处理

4、如果是分片数据,提取头部内容,根据索引,累加到showData里,到全部结束后,处理显示

放上两段示例代码:

发送端:

/// 将图片数据分片发送
    ///
    /// - Parameter imgData: <#imgData description#>
    func sendSmall(imgData:NSData) {

        let count = imgData.length/maxData
        var temp:Data?
        var startFlag:NSMutableData
        var length = 0
        if count>0 {
            for index in 0...count {
                length = index == count ? imgData.length - index*maxData : maxData
                temp = imgData.subdata(with: NSRange(location: index*maxData, length: length))
               
                //头部加序列号
                let str = getMaxLength(str: "flag(index)-(count)")
                startFlag = NSMutableData(data: str.data(using: .utf8)!)
   
                //序号和正文用
分割
                //startFlag.append(GCDAsyncSocket.crlfData())
                startFlag.append(temp! as Data)
                clientSocket?.send(startFlag as Data, toHost: UDPClientViewController.host, port: UInt16(UDPClientViewController.port), withTimeout: -1, tag: 0)
                
              
                
            }
            
            
        }else{
            clientSocket?.send(imgData as Data, toHost: UDPClientViewController.host, port: UInt16(UDPClientViewController.port), withTimeout: -1, tag: 0)

        }
      
    
    
    }
View Code
    func getMaxLength(str:String) -> String {
        var result:String = str
        
        if str.characters.count<10 {
            let len = 10 - str.characters.count
            for _ in 0..<len {
                result.append("a")
            }
        }
        print(result)
        return result
    }
    
View Code

接收端:

/// 只要开始添加了 beginreceiving  这里就可以检测到(这里我就在一个里面实现了 send 并 接收显示)
    ///
    /// - Parameters:
    ///   - sock: <#sock description#>
    ///   - data: <#data description#>
    ///   - address: <#address description#>
    ///   - filterContext: <#filterContext description#>
    func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress address: Data, withFilterContext filterContext: Any?) {
        print("接收到(address)的消息")

        
        if data.count<=maxData && showData == nil {
            if let img = UIImage(data: data){
                imageShow.image = img
            }else{
                print("data  error")
            }

        }else{
            let ten = NSData(data: data).subdata(with: NSRange(location: 0, length: 10))
            var tenStr = String(data: ten as Data, encoding: .utf8)
            print("tenStr:(tenStr)")
            if (tenStr?.contains("flag"))! {
                let imgData = NSData(data: data).subdata(with: NSRange(location: 10, length: data.count-10))
                if showData == nil {
                    showData = NSMutableData(data: imgData)
                }else{
                    showData.append(imgData)
                }
                
                tenStr = tenStr?.replacingOccurrences(of: "flag", with: "")
                tenStr = tenStr?.replacingOccurrences(of: "a", with: "")
                
                let dict:[String] = (tenStr?.components(separatedBy: "-"))!
                if dict.count>1 {
                    let d1 = Int(dict[0])
                    let d2 = Int(dict[1])
                    if d1 == d2 {
                        print(showData.length)
                        if let img = UIImage(data: showData as Data){
                            imageShow.image = img
                            showData = nil
                        }else{
                            print("no img")
                        }
                    }
                }
                
                
            }

        }

        
    }
View Code
原文地址:https://www.cnblogs.com/yajunLi/p/6595509.html