Search code examples
socketsswiftuiudp

Correct way to implement NWConnection.recieve() using UDP


What is a good way to use NWConnection.recieve() to receive data through a UDP socket? All I can think of is a recursive function, but that quickly depletes memory and crashes the app.

p.s. NWConnection.recieveMessage() is the same as NWConnection.recieve() but without a limiting range

The documentation says

Schedules a single receive completion handler for a complete message, as opposed to a range of bytes.

Does this mean if I run it once, it will wait until there is something to listen to, then run the completion handler? I tried this, but it's not working. I try this in listen().

class UDPConnection {
    var connection: NWConnection?
    var response: Data

    init() {
        connection = NWConnection(host: NWEndpoint.Host("192.168.0.32"), port: NWEndpoint.Port(rawValue: 32)!, using: .udp)
        response = Data()
    }

    func begin() {
        guard let connection = connection else {
            print("Connection not initialized.")
            return
        }

        connection.start(queue: .global())
        print("Connection started.")
        self.listen()
    }

    func sendData(_ data: Data) {
        //connection = NWConnection(host: NWEndpoint.Host("192.168.0.32"), port: NWEndpoint.Port(rawValue: 32)!, using: .udp)
        //self.begin()
        guard let connection = connection else {
            print("Connection not initialized.")
            return
        }

        connection.start(queue: .global())

        connection.send(content: data, completion: .contentProcessed { sendError in
            if let error = sendError {
                print("Failed to send data: \(error)")
            } else {
                print("Data sent successfully")
            }
        })

//      DispatchQueue.global().asyncAfter(deadline: .now() + 1.0) {
//          self.closeConnection()
//          print("Connection closed via `DispatchQueue`")
//      }
    }

    func listen() {
        guard let connection = connection else {
            print("Connection not initialized.")
            return
        }

        if connection.state == .ready {
            connection.receiveMessage { (data, _, _, error) in
                print("here2")
                if let error = error {
                    print("Error receiving data: \(error)")
                    return
                }

                print("Data received successfully")
                self.response = data ?? Data([0x65])
                print(self.response)
                self.listen()
            }
            print("here")
        } else {
            print("Connection not ready to receive data.")
            DispatchQueue.global().asyncAfter(deadline: .now() + 0.1) {self.listen()}
        }
    }

    func closeConnection() {
        guard let connection = connection else {
            print("Connection not initialized.")
            return
        }

        connection.cancel()
        print("Connection closed.")
    }
}

Also, if it helps, I get some warnings in the console:

Error creating the CFMessagePort needed to communicate with PPT.

at the start,

nw_connection_set_queue_block_invoke [C1] Error in client: nw_connection_set_queue called after nw_connection_start

Once before I receive the first piece of data, and

nw_connection_set_queue_block_invoke [C1] Error in client: nw_connection_set_queue called after nw_connection_start, backtrace limit exceeded

before every proceeding piece of data.


Solution

  • To initialize the listener, you must create a listener with NWListener, assign NWListener.newConnectionHandler a function containing something to handle the data, and start it using NWListener.start(queue: .global()). Make sure to pass the connection closure parameter to the function so it can receive the data correctly.

    do {listener = try NWListener(using: .udp, on: listenPort)}
    catch {
        print("Failed to create listener: \(error)")
        return
    }
    
    listener?.newConnectionHandler = { [weak self] connection in
        self?.receive(connection)
    }
    
    listener?.start(queue: .global())
    print("Listener started.")
    

    Then, in the receive() function, use connection.receiveMessage() to read the data. connection.receive() can be used to break up the data into separate packets, so recursively run the function to handle the whole message.

    func receive(_ connection: NWConnection) {
        connection.start(queue: .global())
    
        connection.receiveMessage { [weak self] data, context, isComplete, error in
            if let error = error {
                print("Error receiving data: \(error)")
                return
            }
    
            if let data = data {
                print("Recieved \(data).")
                self?.response = data
            } else {
                print("Recieved 0 bytes (no data).")
            }
    
            if isComplete {
                print("Message reception complete. Listening for more messages...")
                self?.receive(connection)
            }
        }
    }
    

    If you don't know what [weak self] is, I recommend this video, by Swiftful Thinking on Youtube.


    Apple's documentation for some functions used:

    NWConnection.recieve()

    NWConnection.recieveMessage()

    NWListener.newConnectionHandler

    More related things can be found my scrolling through the sidebar. Close the open NWConnection/NWListener menu to get a better view of all the categories provided :)