UDP listener only receives one packet

I have an issue where my iPhone app needs to listen for a UDP packet, and respond to the sender. This works when I first load the app, but after receiving the first packet and responding, it stops receiving any more, and I instead see errors in the logs.

Code for UDP Listener:

import Foundation
import Network
import Combine

class UDPListener: ObservableObject {
    
    var listener: NWListener?
    var connection: NWConnection?

    /// New data will be place in this variable to be received by observers
    @Published private(set) public var messageReceived: Data?
    /// When there is an active listening NWConnection this will be `true`
    @Published private(set) public var isReady: Bool = false
    /// Default value `true`, this will become false if the UDPListener ceases listening for any reason
    @Published public var listening: Bool = true
        
    /// A convenience init using Int instead of NWEndpoint.Port
    convenience init(on port: Int) {
        self.init(
            on: NWEndpoint.Port(integerLiteral: NWEndpoint.Port.IntegerLiteralType(port))
        )
    }
    /// Use this init or the one that takes an Int to start the listener
    init(on port: NWEndpoint.Port) {
        let params = NWParameters.udp
        params.allowLocalEndpointReuse = true
        params.allowFastOpen = true
        self.listener = try? NWListener(using: params, on: port)
        self.listener?.stateUpdateHandler = { update in
            switch update {
            case .ready:
                self.isReady = true
                print("Listener connected to port \(port)")
            case .failed, .cancelled:
                // Announce we are no longer able to listen
                self.listening = false
                self.isReady = false
                print("Listener disconnected from port \(port)")
            default:
                print("Listener connecting to port \(port)...")
            }
        }
        self.listener?.newConnectionHandler = { connection in
            print("Listener receiving new message")
            self.createConnection(connection: connection)
        }
        self.listener?.start(queue: .main)
    }
    
    func createConnection(connection: NWConnection) {
        self.connection = connection
        self.connection?.stateUpdateHandler = { (newState) in
            switch (newState) {
            case .ready:
                print("Listener ready to receive message - \(connection)")
                self.receive()
            case .cancelled, .failed:
                print("Listener failed to receive message - \(connection)")
                // Cancel the listener, something went wrong
                self.listener?.cancel()
                // Announce we are no longer able to listen
                self.listening = false
            default:
                print("Listener waiting to receive message - \(connection)")
            }
        }
        self.connection?.start(queue: .main)
    }
    
    func receive() {
        self.connection?.receiveMessage { data, context, isComplete, error in
            if let unwrappedError = error {
                print("Error: NWError received in \(#function) - \(unwrappedError)")
                return
            }

            guard isComplete, let data = data else {
                print("Error: Received nil Data with context - \(String(describing: context))")
                return
            }
            self.messageReceived = data

            print(String(decoding: data, as: UTF8.self))

            if String(decoding: data, as: UTF8.self) == "teststring" {
                switch self.connection?.endpoint {
                case .hostPort(let host, let port):
                    print("Sending response to \(host):\(port)")
                default:
                    break
                }

                self.connection?.send(
                    content: String("received").data(using: .utf8),
                    completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
                        if (NWError == nil) {
                            print("Data was sent to UDP")
                        } else {
                            print("ERROR SEND! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
                        }
                    }))
                )
            }
                                    
            if self.listening {
                self.receive()
            }
            
            print(String(self.listening))
        }
    }
    
    func cancel() {
        print("cancelling udp listener")
        self.listening = false
        self.connection?.cancel()
    }
}

Logs from first packet:

Listener connected to port 20002
Listener receiving new message
Listener waiting to receive message - [C1 connected 192.168.1.117:8829 udp, local: 0.0.0.0:20002, fast-open, definite, attribution: developer, server, path satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, dns]
Listener ready to receive message - [C1 connected 192.168.1.117:8829 udp, local: 0.0.0.0:20002, fast-open, definite, attribution: developer, server, path satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, dns]
teststring
Sending response to 192.168.1.117:8829
true
Data was sent to UDP
2023-08-22 21:36:41.552066-0400 UDPListenerTest[23867:4714028] [] nw_path_evaluator_create_flow_inner NECP_CLIENT_ACTION_ADD_FLOW 529AD533-821D-4076-94A8-C2B7C6BCFF39 [17: File exists]
2023-08-22 21:36:41.552260-0400 UDPListenerTest[23867:4714028] [connection] nw_endpoint_flow_setup_channel [C2 192.168.1.117:8829 initial channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, dns)] failed to request add nexus flow
2023-08-22 21:36:41.552649-0400 UDPListenerTest[23867:4714028] [connection] nw_endpoint_handler_create_from_protocol_listener [C2 192.168.1.117:8829 failed channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, dns)] nw_endpoint_flow_pre_attach_protocols
2023-08-22 21:36:41.552816-0400 UDPListenerTest[23867:4714028] [connection] nw_connection_create_from_protocol_on_nw_queue [C2] Failed to create connection from listener
2023-08-22 21:36:41.553021-0400 UDPListenerTest[23867:4714028] [] nw_ip_channel_inbox_handle_new_flow nw_connection_create_from_protocol_on_nw_queue failed

Then without restarting the app, logs from the second packet:

2023-08-22 21:37:07.561964-0400 UDPListenerTest[23867:4714027] [] nw_path_evaluator_create_flow_inner NECP_CLIENT_ACTION_ADD_FLOW 529AD533-821D-4076-94A8-C2B7C6BCFF39 [17: File exists]
2023-08-22 21:37:07.562156-0400 UDPListenerTest[23867:4714027] [connection] nw_endpoint_flow_setup_channel [C3 192.168.1.117:8829 initial channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, dns)] failed to request add nexus flow
2023-08-22 21:37:07.562510-0400 UDPListenerTest[23867:4714027] [connection] nw_endpoint_handler_create_from_protocol_listener [C3 192.168.1.117:8829 failed channel-flow (satisfied (Path is satisfied), viable, interface: en0[802.11], ipv4, dns)] nw_endpoint_flow_pre_attach_protocols
2023-08-22 21:37:07.562673-0400 UDPListenerTest[23867:4714027] [connection] nw_connection_create_from_protocol_on_nw_queue [C3] Failed to create connection from listener
2023-08-22 21:37:07.562822-0400 UDPListenerTest[23867:4714027] [] nw_ip_channel_inbox_handle_new_flow nw_connection_create_from_protocol_on_nw_queue failed

It looks like you are some how trying to register the same received listening connection twice. A few questions:

  • If you setup a connection management class to handle all of your sending and receiving does this improve your situation? For example you would have a listener management class, to which you mostly have the details for, and then a connection management class that would be created upon newConnectionHandler. Then, this connection class receives or sends data based and sends it off to the other parts of your app to handle appropriately. If you need an echo handler, just store the array of connections that is received, grab the connection the data was received on, and call into the send function in the connection management class to echo back a response.
  • How are you making the client side send here?

Thanks for the direction. Interestingly, it worked fine when using socat:

echo -n teststring | socat - UDP-DATAGRAM:255.255.255.255:20002,broadcast

But would not work otherwise. I think I needed to explicitly close the connection after receiving to get the next packet.

I was able to get this working with the following code:

import Foundation
import Network
import Combine

class UDPConnection {
    let connection: NWConnection
    
    init(connection: NWConnection) {
        self.connection = connection

        self.connection.stateUpdateHandler = { (newState) in
            switch (newState) {
            case .ready:
                print("Connection ready to receive message - \(connection)")
            case .cancelled, .failed:
                print("Connection failed to receive message - \(connection)")
            default:
                print("Connection waiting to receive message - \(connection)")
            }
        }
        self.connection.start(queue: .main)
    }
    
    func receive(done: @escaping () -> Void) {
        switch self.connection.state {
        case .cancelled:
            print("connection state cancelled")
        case .ready:
            print("connection state ready")
        case.failed(let err):
            print("connection state failed: \(err)")
        case .preparing:
            print("connection state preparing")
        case .setup:
            print("connection state setup")
        case .waiting(let err):
            print("connection state waiting: \(err)")
        default:
            print("connection state default")
        }
        
        self.connection.receiveMessage { data, context, isComplete, error in
            if let unwrappedError = error {
                print("Error: NWError received in \(#function) - \(unwrappedError)")
                return
            }

            guard isComplete, let data = data else {
                print("Error: Received nil Data with context - \(String(describing: context))")
                return
            }

            print(String(decoding: data, as: UTF8.self))

            if String(decoding: data, as: UTF8.self) == "teststring" {
                switch self.connection.endpoint {
                case .hostPort(let host, let port):
                    print("Sending response to \(host):\(port)")
                default:
                    break
                }

                self.connection.send(
                    content: String("received").data(using: .utf8),
                    completion: NWConnection.SendCompletion.contentProcessed(({ (NWError) in
                        if (NWError == nil) {
                            print("Data was sent to UDP")
                        } else {
                            print("ERROR SEND! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
                        }

                        self.connection.cancel()
                        done()
                    }))
                )
            }
        }
    }
}

class UDPListener: ObservableObject {
    
    var listener: NWListener?
    var connections: [NWEndpoint: UDPConnection] = [:]

    /// New data will be place in this variable to be received by observers
    @Published private(set) public var messageReceived: Data?
    /// When there is an active listening NWConnection this will be `true`
    @Published private(set) public var isReady: Bool = false
    /// Default value `true`, this will become false if the UDPListener ceases listening for any reason
    @Published public var listening: Bool = true
        
    /// A convenience init using Int instead of NWEndpoint.Port
    convenience init(on port: Int) {
        self.init(
            on: NWEndpoint.Port(integerLiteral: NWEndpoint.Port.IntegerLiteralType(port))
        )
    }

    /// Use this init or the one that takes an Int to start the listener
    init(on port: NWEndpoint.Port) {
        let params = NWParameters.udp
        params.allowLocalEndpointReuse = true
        params.allowFastOpen = true
        self.listener = try? NWListener(using: params, on: port)
        self.listener?.stateUpdateHandler = { update in
            switch update {
            case .ready:
                self.isReady = true
                print("Listener connected to port \(port)")
            case .failed, .cancelled:
                // Announce we are no longer able to listen
                self.listening = false
                self.isReady = false
                print("Listener disconnected from port \(port)")
            default:
                print("Listener connecting to port \(port)...")
            }
        }
        self.listener?.newConnectionHandler = { connection in
            print("Listener receiving new message")
            self.createConnection(connection: connection)
        }
        self.listener?.start(queue: .main)
    }
    
    func createConnection(connection: NWConnection) {
        guard let desc = connection.currentPath?.debugDescription else {return}
        print(desc)

        if (self.connections.index(forKey: connection.endpoint) == nil) {
            let udpConn = UDPConnection(connection: connection)
            print("creating new connection for \(connection.endpoint.debugDescription)")
            self.connections[connection.endpoint] = udpConn
            udpConn.receive {
                self.connections.removeValue(forKey: connection.endpoint)
            }
        } else {
            print("reusing connection for \(connection.endpoint.debugDescription)")
            self.connections[connection.endpoint]?.receive{
                self.connections.removeValue(forKey: connection.endpoint)
            }
        }
    }
    
    func cancel() {
        print("cancelling udp listener")
        self.listening = false
    }
}

I think I needed to explicitly close the connection after receiving to get the next packet.

If you close the connection the connection will attempt to drain the output handler before closing, just as a heads up.
On the client side of your connection, make sure you split apart the send and receive for practical debugging purposes and make sure that the isComplete flag is set correctly in accordance to when your sending / receiving is actually finished.

I have the same problem, I received only one package. Did you manage to solve this problem? When I use localhost (127.0.0.1) it works without problem. Now when I use a router (192.168.0.255) I am tempted to create new ones connected to each packet received.

I have the same problem, I received a package. Am I using the same code as the one posted above? Did I manage to solve this problem? When I use localhost (127.0.0.1) it works without problem. Now when I use through a router (192.168.0.255) it tries to create new ones related to each package received and my logic is exactly the same as its own.

UDP listener only receives one packet
 
 
Q