receiveMessage doesn't seem to be receiving UDP messages

I've found a handful of other posts that seem to be also not answered regarding the Network framework not receiving messages on UDP channels. @Eskimo, whom I perceive as a legend of the apple forums even posted some nearly identical code about a year and a half ago that he said worked where recent posts comment that they are having the same experience as I am.


What I'm doing:

I want to switch my networking framework to use Network framework's NWConnection. Then I want to send and receive on multiple(3) streams to some non-apple device that I connect to via WiFi with NEHotspotConfigurationManager. This worked before with SwiftSocket and CocoaAsyncSocket.


Problem:

NWConnection.receiveMessage(completion:) doesn't seem to get called by UDP, here is my code.


Any ideas what's missing or wrong?



class UDPClient {
    
    var connection: NWConnection
    var address: NWEndpoint.Host
    var port: NWEndpoint.Port
    var delegate: UDPListener?
    private var listening = true
    
    var resultHandler = NWConnection.SendCompletion.contentProcessed { NWError in
        guard NWError == nil else {
            print("ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
            return
        }
    }

    init?(address newAddress: String, port newPort: Int32, listener isListener: Bool = true) {
        guard let codedAddress = IPv4Address(newAddress),
            let codedPort = NWEndpoint.Port(rawValue: NWEndpoint.Port.RawValue(newPort)) else {
                print("Failed to create connection address")
                return nil
        }
        address = .ipv4(codedAddress)
        port = codedPort
        listening = isListener
        connection = NWConnection(host: address, port: port, using: .udp)
            
        connect()
    }
    
    func connect() {
        connection.stateUpdateHandler = { newState in
            switch (newState) {
            case .ready:
                print("State: Ready")
                if self.listening { self.listen() }
            case .setup:
                print("State: Setup")
            case .cancelled:
                print("State: Cancelled")
            case .preparing:
                print("State: Preparing")
            default:
                print("ERROR! State not defined!\n")
            }
        }
        connection.start(queue: .global())
    }
    
    func send(_ data: Data) {
        connection.send(content: data, completion: resultHandler)
    }
    
    private func listen() {
        while listening {
            connection.receiveMessage { data, context, isComplete, error in
                print("Receive isComplete: " + isComplete.description)
                guard let data = data else {
                    print("Error: Received nil Data")
                    return
                }
                print("Data Received") 
            }
        }
    }
}

Accepted Reply

Once your connection is 'ready' you simply need to call receiveMessage() once.


When a message is available your completion handler will be invoked.


Once you have handled that message you can call receiveMessage() again from your completion handler if you want to receive further messages


As far as I can tell from the code you have posted you are blocking the caller of your state update handler by calling your listen method which is simply sitting in a loop calling receiveMessage() which is probably preventing the 'connection' from doing anything else.

Replies

even posted some nearly identical code

Just for the folks reading along at home, that was this post.

It’s hard to say why your code is failing. The one problem I noticed is in your

listen
method. The code you posted doesn’t make sense. Specifically,
receiveMessage(…)
is an async call, so spinning in a loop calling it will result in an unbounded number of async reads all waiting for a message (or an error). That’s going to end badly.

Also, the fact that you have a

listen
method that doesn’t use
NWListener
seems wrong. The support for UDP in
NWConnection
is very ‘connection’ oriented (well, UDP does’t have connections, so let’s say flow oriented). Thus, you create an
NWConnection
in one of two ways:
  • If it’s an outgoing connection, you create it with an address as you’ve shown.

  • If it’s an incoming connection, you create it using a listener.

In both cases the connection is tied to a flow, that is, the tuple of source address, source port, destination address, and destination port.

It wasn’t clear from your description whether your intended use case is compatible with this model. Presumably these non-Apple devices have their own opinion on how to use UDP. What does that look like?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

In response to my listen method I treat the object making the connection like the connection itself. When I am done sending/receiving messages the object that owns the UDPClient is no longer in scope.


I think my misunderstanding is from the documentation. `incoming network connections` sounds like it is listening for connection handshakes.

NWConnection:
A bidirectional data connection between a local endpoint and a remote endpoint.

NWListener:
An object you use to listen for incoming network connections.

https://developer.apple.com/documentation/network/nwconnection

https://developer.apple.com/documentation/network/nwlistener


That's all it says at the moment. So I cannot use NWConnection to listen for UDP messages?


I'll go try out NWListener

Once your connection is 'ready' you simply need to call receiveMessage() once.


When a message is available your completion handler will be invoked.


Once you have handled that message you can call receiveMessage() again from your completion handler if you want to receive further messages


As far as I can tell from the code you have posted you are blocking the caller of your state update handler by calling your listen method which is simply sitting in a loop calling receiveMessage() which is probably preventing the 'connection' from doing anything else.

Indeed, I was trying to use it in the same way as other frameworks where you loop on receiving to receive constant messages. I do believe I will need to do this for my two other UDP streams but now I have it working! Thanks!


class UDPClient {
   
    var connection: NWConnection
    var address: NWEndpoint.Host
    var port: NWEndpoint.Port
   
    var resultHandler = NWConnection.SendCompletion.contentProcessed { NWError in
        guard NWError == nil else {
            print("ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!)")
            return
        }
    }

    init?(address newAddress: String, port newPort: Int32) {
        guard let codedAddress = IPv4Address(newAddress),
            let codedPort = NWEndpoint.Port(rawValue: NWEndpoint.Port.RawValue(newPort)) else {
                print("Failed to create connection address")
                return nil
        }
        address = .ipv4(codedAddress)
        port = codedPort
       
        connection = NWConnection(host: address, port: port, using: .udp)
        connection.stateUpdateHandler = { newState in
            switch (newState) {
            case .ready:
                print("State: Ready")
            case .setup:
                print("State: Setup")
            case .cancelled:
                print("State: Cancelled")
            case .preparing:
                print("State: Preparing")
            default:
                print("ERROR! State not defined!\n")
            }
        }
        connection.start(queue: .global())
    }
   
    deinit {
        connection.cancel()
    }
   
    func sendAndReceive(_ data: Data) {
        self.connection.send(content: data, completion: self.resultHandler)
        self.connection.receiveMessage { data, context, isComplete, error in
            print("Receive isComplete: " + isComplete.description)
            guard let data = data else {
                print("Error: Received nil Data")
                return
            }
            print(String(data: data, encoding: .utf8))
        }
    }
}

I am trying to receive UDP datagrams . I am lost with the connection.receiveMessage(completion:) not being executed. I've used the example above with minor changes:


   

    var connection: NWConnection

    var address: NWEndpoint.Host

    var port: NWEndpoint.Port

   

    var resultHandler = NWConnection.SendCompletion.contentProcessed { NWError in

        guard NWError == nil else {

            print("ERROR! Error when data (Type: Data) sending. NWError: \n \(NWError!)")

            return

        }

    }



    init?(address newAddress: String, port newPort: Int32) {

        let codedAddress = NWEndpoint.Host( newAddress )

        let codedPort = NWEndpoint.Port(rawValue: NWEndpoint.Port.RawValue(newPort)) 

        guard codedPort != nil else {

            print("Failed to create connection port", codedPort as Any)

            return nil

        }

        address = codedAddress

        port = codedPort!

       

        connection = NWConnection(host: address, port: port, using: .udp)

        connection.stateUpdateHandler = { newState in

            switch (newState) {

            case .ready:

                print("State: Ready")

                self.receive()

            case .setup:

                print("State: Setup")

            case .cancelled:

                print("State: Cancelled")

            case .preparing:

                print("State: Preparing")

            default:

                print("ERROR! State not defined!\n")

            }

        }

        connection.start(queue: .global())

    }

   

    deinit {

        connection.cancel()

    }

   

    //func sendAndReceive(_ data: Data) {

    func receive() {

        print("receive starts...")

        //self.connection.send(content: data, completion: self.resultHandler)

        self.connection.receiveMessage { data, context, isComplete, error in

            print("...Receive isComplete: " + isComplete.description)

            guard let data = data else {

                print("...Error: Received nil Data")

                return

            }

            print("...I am not falling through")

            print(String(data: data, encoding: .utf8) as Any)

        }

        print("...or am I?")

    }

}

The print outputs show the init proceeds to ready, but the receiveMessage completion: does not get executed, as the  print("...I am not falling through") does not appear...

State: Preparing

State: Ready

receive starts...

...or am I?

Any idea what goes wrong?

Are you sure that the datagram actually arrived on the receiving device? I recommend that you use an RVI packet trace to confirm that. For details, see Recording a Packet Trace.

As part of that, make sure that tha local port of the NWConnection matches the destination port of the datagram you received.

You wrote:

connection.start(queue: .global())

Don’t use a global concurrent queue for this. Rather:

  • For simple code, use the main queue.

  • If you need anything more complex, create your own custom serial queue.

For more background on this, see Avoid Dispatch Global Concurrent Queues.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I am currently using NWConnection for receiving TCP messages, thanks to your good example two or so years ago. Now I have resorted to implementing listening UDP broadcasts with BlueSocket, due to this issue. While BlueSocket works, I'd prefer NWConnect due to reduced maintenance, dependencies and memory requirements.

So yes, my device or simulator reveives the UDP broadcast packets, and I can see all of them (≈30/s) also in 'sudo tcpdump -K' :

...
12:51:35.676413 IP 192.168.1.29.52091 > 192.168.1.255.10110: UDP, length 38
12:51:35.708019 IP 192.168.1.29.52091 > 192.168.1.255.10110: UDP, length 36
12:51:35.709506 IP 192.168.1.29.52091 > 192.168.1.255.10110: UDP, length 38
12:51:35.726072 IP 192.168.1.29.52091 > 192.168.1.255.10110: UDP, length 26
12:51:35.776199 IP 192.168.1.29.52091 > 192.168.1.255.10110: UDP, length 26
...

Also the port appears to be OK, as the app reports the port as

newPort: 10110
coded port: Optional(10110)

The example is from my source simulator app broadcasting to the client app/Xcode simulator on the same machine. The behaviour Is same between a real source device communicating with the same client app running on iOS.

NWConnection does not, in general, support UDP broadcasts [1]. The correct API for this is NWConnectionGroup but, based on my conversation with the Network framework team last Friday, that won’t work for broadcasts [2]. My advice is:

  • File an enhancement request against Network framework for UDP broadcast support.

  • In the meantime, continue working with your favourite BSD Sockets wrapper

Please post your bug number, just for the record.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

[1] There is a very limited case where you can use NWListener to spawn NWConnection instances based on incoming broadcasts, but that’s not what you need.

[2] It works for multicasts, like NWMulticastGroup, but that class doesn’t support broadcasts.

Thank you for the information & advice.

I've submitted an enhancement request via the Feedback assistant (Dec 12, 2022 at 4:54 PM – FB11869841).

Thanks,

Add a Comment