CocoaAsyncSocket UDP發送數據超過包大小限制(Message too long)

最近在作iOS上,基於UDP傳輸音視頻時遇到的一個問題,這邊紀錄一下:git

因爲考慮實時性比較高,因此採用了 CocoaAsyncSocket 的UDP框架來實現,將視頻切割成一幀幀的圖片發給服務端,不過,在發送圖片的過程當中,發現:github

當圖片大於9k大小時,會發送失敗;緩存

在didclose代理方法裏,會打印錯誤信息:Message too longapp

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

 

並且senddata成功或失敗的都跳過了,沒有執行框架

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

 

其實就是數據太長,致使socket直接關閉了。。。socket

 

查了好些資料,發如今OS X上,因爲是由於:默認狀況下,OSX具備有限的最大是9216個字節的UDP包。async

這樣就阻止了超過大小的包的發送。ide

而後,有一種辦法,是經過終端讓系統增大限制數;測試

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

這樣執行完,在模擬器上運行,的確是能夠實現超過9k的圖片的發送,不過在真機上,就沒辦法了。。。spa

 

若是想查看udp其餘信息,這樣:

sudo sysctl -w net.inet.udp

 

 

不過,這種辦法,並非最終的解決辦法,因此不知道還有沒有更好的辦法呢。。。

 

參考資料:

一、UDP Message too long

二、set max packet size for GCDAsyncUdpSocket

三、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,若是想用分片,也是能夠解決的

思路大概這樣:

發送端:

一、對要發送的圖片先處理:大於9000的分片屢次發送

二、每次發送的片帶上一個頭,三部分組成:分片標示符、頁碼數據、補充數據,且限定10位

    a:分片標示符表示這段數據是分片,須要分片處理,好比用字符「flag」(後來我發現不加標識符好像也能夠,看我的了)

    b:頁碼數據包含當前頁和總頁,二者用字符-分割(分割是爲了接收方法裏截取),總頁至關於索引總頁,好比有3段,就是0、一、2中的2,能夠用餘數理解。

    c:若是前兩段不滿10位,不足用字符a補齊(a是舉例,能夠自行更換)

這樣好比:一張圖片有20k,會分紅3段發送,以下:

flag0-2aaa+分片數據

flag1-2aaa+分片數據

flag2-2aaa+分片數據   

解釋:構成一個頭,而後再拼接上真實的圖片數據,兩部分組合進行發送。

 

接收端:

一、先定義一過全局可變data類型屬性(NSMutableData),用於封裝一段段分片,好比:

var showData:NSMutableData! = nil

二、每次在didReceive裏,先根據data和showData判斷是不是分片數據

三、若是不是分片,直接處理

四、若是是分片數據,提取頭部內容,根據索引,累加到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)

        }
      
    
    
    }
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
相關文章
相關標籤/搜索