How to Reply to a Message Received by Multicast Receiver and Extract Connection for Communication

Hello everyone,

I’m currently working on a Swift project using the Network framework to create a multicast-based communication system. Specifically, I’m implementing both a multicast receiver and a sender that join the same multicast group for communication. However, I’ve run into some challenges with the connection management, replying to multicast messages, and handling state updates for both connections and connection groups.

Below is a breakdown of my setup and the specific issues I’ve encountered.

I have two main parts in the implementation: the multicast receiver and the multicast sender. The goal is for the receiver to join the multicast group, receive messages from the sender, and send a reply back to the sender using a direct connection.

Multicast Receiver Code:

import Network
import Foundation

func setupMulticastGroup() -> NWConnectionGroup? {
    let multicastEndpoint1 = NWEndpoint.hostPort(host: NWEndpoint.Host("224.0.0.1"), port: NWEndpoint.Port(rawValue: 45000)!)
    let multicastParameters = NWParameters.udp
    multicastParameters.multipathServiceType = .aggregate

    do {
        let multicastGroup = try NWMulticastGroup(for: [multicastEndpoint1], from: nil, disableUnicast: false)
        let multicastConnections = NWConnectionGroup(with: multicastGroup, using: multicastParameters)
        multicastConnections.stateUpdateHandler = InternalConnectionStateUpdateHandler
        multicastConnections.setReceiveHandler(maximumMessageSize: 16384, rejectOversizedMessages: false, handler: receiveHandler)
        multicastConnections.newConnectionHandler = newConnectionHandler
        multicastConnections.start(queue: .global())
        
        return multicastConnections
    } catch {
        return nil
    }
}

func receiveHandler(message: NWConnectionGroup.Message, content: Data?, isComplete: Bool) {
    print("Received message from \(String(describing: message.remoteEndpoint))")

    if let content = content, let messageString = String(data: content, encoding: .utf8) {
        print("Received Message: \(messageString)")
    }

    let remoteEndpoint = message.remoteEndpoint
    message.reply(content: "Multicast group on 144 machine ACK from recv handler".data(using: .utf8))

    if let connection = multicastConnections?.extract(connectionTo: remoteEndpoint) {
        connection.stateUpdateHandler = InternalConnectionRecvStateUpdateHandler
        connection.start(queue: .global())
        connection.send(content: "Multicast group on 144 machine ACK from recv handler".data(using: .utf8), completion: NWConnection.SendCompletion.contentProcessed({ error in
            print("Error code: \(error?.errorCode ?? 0)")
            print("Ack sent to \(connection.endpoint)")
        }))
    }
}

func newConnectionHandler(connection: NWConnection) {
    connection.start(queue: .global())
    connection.send(content: "Multicast group on 144 machine ACK".data(using: .utf8), completion: NWConnection.SendCompletion.contentProcessed({ error in
        print("Error code: \(error?.errorCode ?? 0)")
        print("Ack sent to \(connection.endpoint)")
    }))
}

func InternalConnectionRecvStateUpdateHandler(_ pState: NWConnection.State) {
    switch pState {
    case .setup:
        NSLog("The connection has been initialized but not started")
    case .preparing:
        NSLog("The connection is preparing")
    case .waiting(let error):
        NSLog("The connection is waiting for a network path change. Error: \(error)")
    case .ready:
        NSLog("The connection is established and ready to send and receive data.")
    case .failed(let error):
        NSLog("The connection has disconnected or encountered an error. Error: \(error)")
    case .cancelled:
        NSLog("The connection has been canceled.")
    default:
        NSLog("Unknown NWConnection.State.")
    }
}

func InternalConnectionStateUpdateHandler(_ pState: NWConnectionGroup.State) {
    switch pState {
    case .setup:
        NSLog("The connection has been initialized but not started")
    case .waiting(let error):
        NSLog("The connection is waiting for a network path change. Error: \(error)")
    case .ready:
        NSLog("The connection is established and ready to send and receive data.")
    case .failed(let error):
        NSLog("The connection has disconnected or encountered an error. Error: \(error)")
    case .cancelled:
        NSLog("The connection has been canceled.")
    default:
        NSLog("Unknown NWConnection.State.")
    }
}
let multicastConnections = setupMulticastGroup()
RunLoop.main.run()

Multicast Sender Code:

import Foundation
import Network
func setupConnection() -> NWConnection {
    let params = NWParameters.udp
    params.allowLocalEndpointReuse = true
    return NWConnection(to: NWEndpoint.hostPort(host: NWEndpoint.Host("224.0.0.1"), port: NWEndpoint.Port(rawValue: 45000)!), using: params)
}

func sendData(using connection: NWConnection, data: Data) {
    connection.send(content: data, completion: .contentProcessed { nwError in
        if let error = nwError {
            print("Failed to send message with error: \(error)")
        } else {
            print("Message sent successfully")
        }
    })
}
func setupReceiveHandler(for connection: NWConnection) {
    connection.receive(minimumIncompleteLength: 1, maximumLength: 65000) { content, contentContext, isComplete, error in
        print("Received data:")
        print(content as Any)
        print(contentContext as Any)
        print(error as Any)
     
        setupReceiveHandler(for: connection)
    }
}

let connectionSender = setupConnection()
connectionSender.stateUpdateHandler = internalConnectionStateUpdateHandler
connectionSender.start(queue: .global())

let sendingData = "Hello, this is a multicast message from the process on mac machine 144".data(using: .utf8)!

sendData(using: connectionSender, data: sendingData)
setupReceiveHandler(for: connectionSender)
RunLoop.main.run()

Issues Encountered:

  1. Error Code 0 Even When Connection Refused:

On the receiver side, I encountered this log:

nw_socket_get_input_frames [C1.1.1:1] recvmsg(fd 8, 9216 bytes) [61: Connection refused]
Error code: 0
Ack sent to 10.20.16.144:62707

Questions:

  1. how do I reply to the message if above usage pattern is wrong?
  2. how do I get a NWConnection from the received message to create a separate connection for communication with the sender.

Any insights or suggestions on resolving these issues or improving my multicast communication setup would be greatly appreciated.

Thanks :)

Also posted the same on swift forums in hopes of getting swifter response :( here's the link: https://forums.swift.org/t/how-to-reply-to-a-message-received-by-multicast-receiver-and-extract-connection-for-communication/75454

Lemme take a step back and ask about the big picture. Why are you doing this?

Why do I ask? Because my experience is that folks get into multicast for two reasons:

  • They’re implementing local network service discovery, in which case it’s much easier to use Bonjour.

  • They think it’ll be a performance boost, which is often not the case. My Wi-Fi Fundamentals post explains why.

Share and Enjoy

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

Why are you doing this?

@DTS Engineer To clarify our situation:

  1. While we are exploring mDNS-based discovery using Bonjour, our primary goal is to create cross-platform applications that can run on the same network and discover each other using IP-based multicast. We're facing challenges in this area and would like to better understand why these issues occur and how we can resolve them when using NWConnectionGroup.

  2. Unlike traditional service discovery, our use case isn’t about discovering specific services. Instead, we are focused on process discovery—identifying processes running on the same network, regardless of whether they expose a service. This changes the approach we need to take compared to service-based discovery.

  3. We are avoiding the use of multicast DNS (mDNS) because it adds overhead and costs. For a simple use case like ours, where IP multicast works seamlessly on other platforms, we want to avoid the complexity and resource consumption associated with mDNS queries.

  4. Additionally, when we use NWConnection to send a multicast request and the responder is a non-Apple process (e.g., a Linux process using BSD sockets), we notice that the responses from that process are not received by the NWConnection, unlike what happens in similar scenarios on other platforms. We are curious about why this occurs and how we can resolve it?

Any guidance on how to handle these multicast challenges more effectively would be greatly appreciated.

Apologies for the delayed response :)

How to Reply to a Message Received by Multicast Receiver and Extract Connection for Communication
 
 
Q