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)!) //序号和正文用\r\n分割 //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) } }
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 }
接收端:
/// 只要开始添加了 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") } } } } } }