How to restart NWListener once it was cancelled?

只愿长相守 提交于 2021-01-28 11:52:06

问题


I would like to receive some data via UDP in an endless loop until the user cancels the data receiving. I created an NWListener, and set it up. It receives data as expected. However once I call .cancel() on my NWListener it changes the state to .cancelled but afterwards I have no chance to restart the listening process again. It blocks somehow the port. I would be able to restart it on another port, but this is not what I want to do. On the same port it ends up in this error message:


2020-02-20 23:00:28.957501+0100 networking[10942:7110395] [] nw_path_evaluator_evaluate NECP_CLIENT_ACTION_ADD error [48: Address already in use]
2020-02-20 23:00:28.957642+0100 networking[10942:7110395] [] nw_path_create_evaluator_for_listener nw_path_evaluator_evaluate failed
2020-02-20 23:00:28.957735+0100 networking[10942:7110395] [] nw_listener_start_locked [L2] nw_path_create_evaluator_for_listener failed
👂🏼👂🏼👂🏼 NWListener Handler called
Listener: Failed POSIXErrorCode: Address already in use

I created a dedicated sample project in Xcode to nail the issue down as good as possible and to ensure it has nothing to do with the rest of my actual project. This is the ViewController I created to show the problem, I just connected two buttons to start/stop UDP listening:


import UIKit
import Network

class ViewController: UIViewController {

    var udpListener:NWListener?
    var backgroundQueueUdpListener   = DispatchQueue(label: "udp-lis.bg.queue", attributes: [])
    var backgroundQueueUdpConnection = DispatchQueue(label: "udp-con.bg.queue", attributes: [])

    override func viewDidLoad() {
        super.viewDidLoad()

        myOnButton(self)
    }

    @IBAction func myOnButton(_ sender: Any) {

        do {
            self.udpListener = try NWListener(using: .udp, on: 55555)
            self.udpListener?.stateUpdateHandler = { (listenerState) in
                print("👂🏼👂🏼👂🏼 NWListener Handler called")
                switch listenerState {
                case .setup:
                    print("Listener: Setup")
                case .waiting(let error):
                    print("Listener: Waiting \(error)")
                case .ready:
                    print("Listener: ✅ Ready and listens on port: \(self.udpListener?.port?.debugDescription ?? "-")")
                case .failed(let error):
                    print("Listener: Failed \(error)")
                case .cancelled:
                    print("Listener: 🛑 Cancelled by myOffButton")
                default:
                    break;

                }
            }

            self.udpListener?.start(queue: backgroundQueueUdpListener)
            self.udpListener?.newConnectionHandler = { (incomingUdpConnection) in
                print("📞📞📞 NWConnection Handler called ")
                incomingUdpConnection.stateUpdateHandler = { (udpConnectionState) in

                    switch udpConnectionState {
                    case .setup:
                        print("Connection: 👨🏼‍💻 setup")
                    case .waiting(let error):
                        print("Connection: ⏰ waiting: \(error)")
                    case .ready:
                        print("Connection: ✅ ready")
                        self.processData(incomingUdpConnection)
                    case .failed(let error):
                        print("Connection: 🔥 failed: \(error)")
                    case .cancelled:
                        print("Connection: 🛑 cancelled")
                    default:
                        break
                    }
                }

                incomingUdpConnection.start(queue: self.backgroundQueueUdpConnection)
            }

        } catch {
            print("🧨🧨🧨 CATCH")
        }

    }
    @IBAction func myOffButton(_ sender: Any) {
        udpListener?.cancel()
    }

    func processData(_ incomingUdpConnection :NWConnection) {

        incomingUdpConnection.receiveMessage(completion: {(data, context, isComplete, error) in

            if let data = data, !data.isEmpty {
                if let string = String(data: data, encoding: .ascii) {
                    print ("DATA       = \(string)")
                }
            }
            //print ("context    = \(context)")
            print ("isComplete = \(isComplete)")
            //print ("error      = \(error)")

            self.processData(incomingUdpConnection)
        })

    }

}

I hope someone can help me, I spent now several hours to solve this issue, but without any success so far.

To test incoming messages via UDP, I used this shell script:

#!/bin/zsh
while :
do
    echo "1234" | nc -cu -w 0 192.168.2.106 55555
    sleep 1
    echo "567890" | nc -cu -w 0 192.168.2.106 55555
    sleep 1
done


回答1:


You need to clean up all of the resources correctly; You need to keep track of your NWConnections and make sure you cancel any when you cancel your listener, otherwise the socket isn't available for reuse.

I added an array to track the connections and ensure that all connections are cancelled when the lister is stopped.

To avoid infinite loops it also also important that you don't call processData when there is an error on the connection.

var udpListener:NWListener?
var backgroundQueueUdpListener   = DispatchQueue(label: "udp-lis.bg.queue", attributes: [])
var backgroundQueueUdpConnection = DispatchQueue(label: "udp-con.bg.queue", attributes: [])

var connections = [NWConnection]()

override func viewDidLoad() {
    super.viewDidLoad()

    myOnButton(self)
}

@IBAction func myOnButton(_ sender: Any) {

    guard self.udpListener == nil else {
        print("Already listening. Not starting again")
        return
    }

    do {
        self.udpListener = try NWListener(using: .udp, on: 55555)
        self.udpListener?.stateUpdateHandler = { (listenerState) in
            print("👂🏼👂🏼👂🏼 NWListener Handler called")
            switch listenerState {
            case .setup:
                print("Listener: Setup")
            case .waiting(let error):
                print("Listener: Waiting \(error)")
            case .ready:
                print("Listener: ✅ Ready and listens on port: \(self.udpListener?.port?.debugDescription ?? "-")")
            case .failed(let error):
                print("Listener: Failed \(error)")
                self.udpListener = nil
            case .cancelled:
                print("Listener: 🛑 Cancelled by myOffButton")
                for connection in self.connections {
                    connection.cancel()
                }
                self.udpListener = nil
            default:
                break;

            }
        }

        self.udpListener?.start(queue: backgroundQueueUdpListener)
        self.udpListener?.newConnectionHandler = { (incomingUdpConnection) in
            print("📞📞📞 NWConnection Handler called ")
            incomingUdpConnection.stateUpdateHandler = { (udpConnectionState) in

                switch udpConnectionState {
                case .setup:
                    print("Connection: 👨🏼‍💻 setup")
                case .waiting(let error):
                    print("Connection: ⏰ waiting: \(error)")
                case .ready:
                    print("Connection: ✅ ready")
                    self.connections.append(incomingUdpConnection)
                    self.processData(incomingUdpConnection)
                case .failed(let error):
                    print("Connection: 🔥 failed: \(error)")
                    self.connections.removeAll(where: {incomingUdpConnection === $0})
                case .cancelled:
                    print("Connection: 🛑 cancelled")
                    self.connections.removeAll(where: {incomingUdpConnection === $0})
                default:
                    break
                }
            }

            incomingUdpConnection.start(queue: self.backgroundQueueUdpConnection)
        }

    } catch {
        print("🧨🧨🧨 CATCH")
    }

}
@IBAction func myOffButton(_ sender: Any) {
    udpListener?.cancel()
}

func processData(_ incomingUdpConnection :NWConnection) {

    incomingUdpConnection.receiveMessage(completion: {(data, context, isComplete, error) in

        if let data = data, !data.isEmpty {
            if let string = String(data: data, encoding: .ascii) {
                print ("DATA       = \(string)")
            }
        }
        //print ("context    = \(context)")
        print ("isComplete = \(isComplete)")
        if error == nil {
            self.processData(incomingUdpConnection)
        }
    })

}


来源:https://stackoverflow.com/questions/60329330/how-to-restart-nwlistener-once-it-was-cancelled

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!