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 NWConnection
s 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
Cannot Convert Value of Type 'X' to Expected Argument Type 'X'
Shorthand for Wrapping a Swift Variable in an Optional
Swiftui CPU High Usage on Real-Time Foreach View Updating (Macos)
How to Fix Cannot Find 'Firebaseapp' in Scope
Difference When Declaring Swift Protocol Using Inheritance from Another Protocol or Using Where Self
Swift Wkwebview: Can't Find Variable Error When Calling Method
Nscollectionview Selection Handling in Swift
How to a Convert a Dictionary Slice to a Dictionary in Swift
How to Use Axobserveraddnotification in Swift
Swift: Nested Optionals in a Single Guard Statement
Nstoolbarflexiblespaceitem Is Constraint to Nssplitviewitem in Swift
Decrypted String Always Returning Null
Reading from Txt File in Swift 3
Macos Security Scoped Url Bookmark for Folder
Negative Arrayslice: Index Is Out of Range
Xcode - Upgrade to Swift 4 Manually
Dynamic Dispatching Protocol Extension Doesn't Work Multiple Targets