Swift Nwlistener Listen, Cancel, and Relisten Successfully

How to restart NWListener once it was cancelled?

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

}

Swift UDP Connection Issue

In your function receive you are using the NWConnection.receiveMessage if you check the documentation here:

https://developer.apple.com/documentation/network/nwconnection/3020638-receivemessage

You'll see that it schedules a single receive completion handler. That means that you'll have to do something to trigger it again. What I normally do is have a function like:

private func setupReceive() {
connection.receive(minimumIncompleteLength: 1, maximumLength: MTU) { (data, _, isComplete, error) in
if let data = data, !data.isEmpty {
let message = String(data: data, encoding: .utf8)
print("connection \(self.id) did receive, data: \(data as NSData) string: \(message ?? "-")")
self.send(data: data)
}
if isComplete {
self.connectionDidEnd()
} else if let error = error {
self.connectionDidFail(error: error)
} else {
self.setupReceive() // HERE I SET THE RECEIVE AGAIN
}
}
}

That way, after processing the read, you end up setting up another single receive completion handler.

If you want to see a full example, you can check my article on using Network.framework here:

https://rderik.com/blog/building-a-server-client-aplication-using-apple-s-network-framework/

The example uses TCP instead of UDP, but it should give a general idea.

Detecting a client disconnecting with UDP using Network.framework

A UDP server gets no information via the network about whether a UDP client has disconnected or gone away, unless perhaps the client explicitly sends a some sort of additional message (via UDP, TCP, or other side channel) regarding its disconnect status. So there's nothing to change the NWConnection state (except perhaps some sort of problem with the server itself).

Perhaps the server can assume a disconnect after some agreed upon or negotiated timeout time has passed without some sort of activity. Or number of packets, bytes of data. Etc. And close the connection itself.



Related Topics



Leave a reply



Submit