Как перезапустить NWListener после его отмены? - PullRequest
0 голосов
/ 21 февраля 2020

Я хотел бы получать некоторые данные через UDP в бесконечном количестве l oop, пока пользователь не отменит получение данных. Я создал NWListener и настроил его. Он получает данные, как и ожидалось. Однако, когда я вызываю .cancel () на моем NWListener , он меняет состояние на .cancelled , но после этого у меня нет шансов снова запустить процесс прослушивания. Это как-то блокирует порт. Я мог бы перезапустить его на другой порт, но это не то, что я хочу сделать. На том же порту это заканчивается в этом сообщении об ошибке:


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

Я создал специальный проект примера в XCode, чтобы решить проблему как можно лучше и гарантировать, что это не имеет никакого отношения к остальной части мой актуальный проект. Это ViewController, который я создал, чтобы показать проблему, я просто подключил две кнопки для запуска / остановки прослушивания UDP:


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)
        })

    }

}

Я надеюсь, что кто-то может мне помочь, я потратил сейчас несколько часов, чтобы решить эту проблему, но пока безуспешно.

Чтобы проверить входящие сообщения через UDP, я использовал этот сценарий оболочки:

#!/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 Ответ

2 голосов
/ 21 февраля 2020

Вам необходимо правильно очистить все ресурсы; Вам необходимо отслеживать ваши NWConnection s и обязательно отменять их при отмене слушателя, иначе сокет не будет доступен для повторного использования.

Я добавил массив для отслеживания соединений и обеспечения того, чтобы все соединения отменяются при остановке списка.

Чтобы избежать бесконечных циклов, также важно, чтобы вы не вызывали processData, когда в соединении возникает ошибка.

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)
        }
    })

}
...