首页 > 技术文章 > Swift NWListener 监听udp 只能监听一次解决方法

17years 2021-09-10 15:37 原文

这个功能应该是整个 app 的开端,但是也同时困扰了我好久。一度让我有些许的崩溃!

最开始想要去找一个三方库然后引入到项目之中,但是!!!刚接触ios开发的我并不是很熟悉,最开始甚至不知道有 pod 这么方便的东西存在。

所以一顿折腾度娘之后找到了苹果官方的udp连接类 :NWConnection

最开始只是简单的连接对面大佬的udp,代码我粘在下面了(很简单不做过多的讲解):

     // serviceIP 为对方udp的ip , host 为对方udp监听的端口号
connection = NWConnection(host: serviceIP, port: port, using: .udp); connection!.start(queue: .global()) let contentToSend="hello world".data(using: String.Encoding.utf8) connection!.send(content: contentToSend, completion: NWConnection.SendCompletion.contentProcessed({(NWError) in if NWError==nil{ print("Data was sent to UDP") }else{ print("ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!) ") } }))

 

发送很简单,但是到了监听差点没难死我(我严重怀疑这是苹果的bug)

监听的时候用 NWListener 类

这也是不少人选择的一个监听类,从类的名字就可以看出来这货就是专业监听户,最开始的代码我放在下面了(不要急着扒,这个代码是有问题的!):

    func initUdpListen() -> Void {
        do{
            try listen = NWListener(using: .udp, on: listenPort)
            listen?.stateUpdateHandler = { [self](newState) in
                switch newState {
                case .failed:
                    startUdpListen()
                default:
                    break
                }
            }
            listen?.newConnectionHandler = {(newConnection) in
                newConnection.stateUpdateHandler = { [self]newState in
                    switch newState {
                    case .ready:
                        receiveUDP(on: newConnection)case .failed:
                        startUdpListen()
                    default:
                        break
                    }
                }
                newConnection.start(queue: DispatchQueue(label: "UDP Server Queue"))
            }
        }
        catch {
                print("unable to create listener")
        }
        listen?.start(queue: .main)
    }
    func receiveUDP(on listenConnect: NWConnection){
        listenConnect.receiveMessage { (data, context, isComplete, error) in
            if let error = error {
                print("udp",error)
                return
            }
            if let data = data, !data.isEmpty {
                let backToString = String(decoding: data, as: UTF8.self)
                print("\(backToString)")
            }
       receiveMessageUDP(on: listenConnect) } }

网上好多人都是这么写的,看上去没有什么问题

但是!!!

当你使用 udp 单播的时候是没有问题,一旦使用组播,那就神奇了,你只能接收到一次,之后整个udp就是不好使的了!

各种去查询,但是万万没有想到,我的天啊。这个问题好像是无解,而当我看到苹果官方的文档之后我一度崩溃

 

 

 

可能是本身这个 newConnectionHandle 就是有问题的,所以苹果官方在最新的bate版本中新加入了 newConnectionGroupHandle 。但是我这个项目不可能等 bate 之后再继续呀,所以我想了一个比较狗的方法!它不是还好使一次呢么,那我就接收一次就让它重新初始化一个呗。于是有了并不优雅的改进版:

      var listen :NWListener? = nil

    func initUdpListen() -> Void {

do{
            try listen = NWListener(using: .udp, on: listenPort)
            listen?.stateUpdateHandler = { [self](newState) in
                switch newState {
                case .failed:
                    startUdpListen()
                default:
                    break
                }
            }
            listen?.newConnectionHandler = {(newConnection) in
                newConnection.stateUpdateHandler = { [self]newState in
                    switch newState {
                    case .ready:
                        receiveUDP(on: newConnection)
                    case .cancelled:
                        listen = nil
                        print("udp listen","udp cancelled")
                        initUdpListen()
                    case .failed:
                        startUdpListen()
                    default:
                        break
                    }
                }
                newConnection.start(queue: DispatchQueue(label: "UDP Server Queue"))
            }
        }
        catch {
                print("unable to create listener")
        }
        listen?.start(queue: .main)
    }
    
    
    func receiveUDP(on listenConnect: NWConnection){
        listenConnect.receiveMessage { (data, context, isComplete, error) in
            if let error = error {
                print("udp",error)
                return
            }
            if let data = data, !data.isEmpty {
                let backToString = String(decoding: data, as: UTF8.self)
                print("\(backToString)")
            }
            listenConnect.cancel()
            self.listen?.cancel()
        }
    }

当 receiveUDP 函数执行之后,我就让 listen 直接注销,然后再 listen 的 stateUpdateHandler 的 .cancel 状态直接重新初始化整个 NWListener (上一个忘记写 listen 的初始化定义了)

 

这方法虽然糙了点,但是好歹功能是可以实现了。现在就期待着新 swift 能把这个神奇的问题改掉吧!

 

最后在结尾插一个 iOS 获取本机局域网 IP 的方法

    
    //获取本机ip
    func getLocalIPAddressForLAN() -> String? {
        var address: String?
        // get list of all interfaces on the local machine
        var ifaddr: UnsafeMutablePointer<ifaddrs>? = nil
        guard getifaddrs(&ifaddr) == 0 else {
            return nil
        }
        guard let firstAddr = ifaddr else {
            return nil
        }
        for ifptr in sequence(first: firstAddr, next: { $0.pointee.ifa_next }) {
            let interface = ifptr.pointee
            // Check for IPV4 or IPV6 interface
            let addrFamily = interface.ifa_addr.pointee.sa_family
            if addrFamily == UInt8(AF_INET) || addrFamily == UInt8(AF_INET6) {
                // Check interface name
                let name = String(cString: interface.ifa_name)
                if name == "en0" {
                    // Convert interface address to a human readable string
                    var addr = interface.ifa_addr.pointee
                    var hostName = [CChar](repeating: 0, count: Int(NI_MAXHOST))
                    getnameinfo(&addr,
                                socklen_t(interface.ifa_addr.pointee.sa_len),
                                &hostName, socklen_t(hostName.count), nil, socklen_t(0), NI_NUMERICHOST)
                    address = String(cString: hostName)
                }
            }
        }
        freeifaddrs(ifaddr)
        return address
    }

亲测没有毛病!

 

推荐阅读