swiftserver-side-swiftnetwork.framework

Detecting a client disconnecting with UDP using Network.framework


I'm trying to determine when a UDP client stops sending packets to a server when using Network.framework

I've built out a small example which demonstrates the server failing to change states to .cancelled when a client's connection is cancelled.

Example Client:

import Foundation
import Network

func sendMessage(on connection: NWConnection) {
    connection.send(content: "hello".data(using: .utf8), completion: .contentProcessed({error in
        if let error = error {
            print("error while sending hello: \(error)")
            return

        }

        connection.receiveMessage {data, context, isComplete, error in
            if let error = error {
                print("error while receiving reply: \(error)")
                return

            }

            connection.cancel()

        }

    }))
}

var connection: NWConnection = {
    let connection = NWConnection(
        to: .service(
            name: "Hello",
            type: "_test._udp",
            domain: "local",
            interface: nil
        ),
        using: .udp
    )

    connection.stateUpdateHandler = {newState in
        switch newState {
        case .ready:
            sendMessage(on: connection)
        case .failed(let error):
            print("client failed with error: \(error)")
        case .cancelled:
            print("Cancelled connection")
        default:
            break
        }
    }

    return connection
}()

connection.start(queue: DispatchQueue(label: "test"))

RunLoop.main.run()

Example Server:

import Foundation
import Network

func receive(on connection: NWConnection) {
    connection.receiveMessage { (data, context, isComplete, error) in
        if let error = error {
            print(error)
            return

        }

        connection.send(content: "world".data(using: .utf8), completion: .contentProcessed({error in
            if let error = error {
                print("error while sending data: \(error)")
                return

            }

        }))

        receive(on: connection)

    }

}

var listener: NWListener = {
    let listener = try! NWListener(using: .udp)

    listener.service = NWListener.Service(name: "Hello", type: "_test._udp", domain: nil, txtRecord: nil)
    listener.newConnectionHandler = {newConnection in
        newConnection.stateUpdateHandler = {newState in
            switch newState {
            case .ready:
                receive(on: newConnection)
            case .failed(let error):
                print("client failed with error: \(error)")
            case .cancelled:
                print("Cancelled connection")
            default:
                break
            }
        }

        newConnection.start(queue: DispatchQueue(label: "new client"))


    }
    return listener
}()

listener.start(queue: DispatchQueue(label: "test"))

RunLoop.main.run()

When running the client while the server is running, the client sends and receives one packet and then is cancelled. The client prints Connection cancelled. However, the state of the NWConnection on the server does not change, and connection.receiveMessage fails silently when no data to read from the client.

I would expect either the server connection's state to change or receiveMessage to call its completion handler despite no data being present (data is Data? after all)

So, I'm unsure how to detect when a client stops sending packets when using a UDP server on Network.framework. How should I go about detecting "disconnected" clients?


Solution

  • 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.