Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Detecting a client disconnecting with UDP using Network.framework

Tags:

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?

like image 604
Emma K Alexandra Avatar asked Jan 26 '19 03:01

Emma K Alexandra


1 Answers

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.

like image 73
hotpaw2 Avatar answered Sep 19 '22 08:09

hotpaw2