Problem
I am trying to send out a broadcast using NWConnection
and then listen for responses using NWListener
on port 50913. Although the broadcast is sent out correctly (= no error is thrown upon sending), I only get responses to my broadcast from what I suppose are the network interfaces of my own MacBook. In other words, it seems like the broadcast is never really submitted to the network.
Context
I don't have in-depth knowledge about the behavior of UDP which is why I am confused about this behavior. I've been reading online about this and couldn't find anything really related to the behavior I am experiencing. I've also looked at this developer forums entry and implemented the broadcast accordingly. The response from @meaton
does not suggest that broadcasts are not supported by NWConnection
(which is what I thought to be the culprit initially), and I am not getting the error they are talking about in their post, but a behavior that is entirely different.
Does anyone know what is wrong with my implementation?
Code
final public class BroadcastDiscoveryEngine {
private let logger: Logger = Logger.init(for: BroadcastDiscoveryEngine.self)
private let broadcastConnection: NWConnection
private let broadcastResponseListener: NWListener
private let responseParser: BroadcastResponseParser = BroadcastResponseParser()
private var discoveryContinuation: AsyncStream<Discovery>.Continuation? = nil
init() throws {
let parameters = NWParameters.udp
parameters.allowLocalEndpointReuse = true
parameters.allowFastOpen = true
parameters.includePeerToPeer = true
broadcastConnection = NWConnection(host: .ipv4(.broadcast), port: .init(integerLiteral: 50913), using: parameters)
broadcastResponseListener = try NWListener(using: parameters, on: 50913)
}
func startBroadcast(continuation: AsyncStream<Discovery>.Continuation) {
discoveryContinuation = continuation
broadcastConnection.stateUpdateHandler = handleBroadcastConnectionStateUpdate(state:)
broadcastConnection.start(queue: .global(qos: .default))
startBroadcastListener()
}
func stopBroadcast() {
broadcastConnection.cancel()
broadcastResponseListener.cancel()
}
private func sendBroadcastMessage() {
broadcastConnection.send(content: "my_broadcast_message".data(using: .utf8), completion: .contentProcessed({ error in
if let error = error {
self.logger.error("Sending broadcast message failed with error: \(error.debugDescription, privacy: .public)")
self.broadcastConnection.cancel()
self.broadcastResponseListener.cancel()
}
self.logger.info("Broadcast message sent.")
}))
}
private func handleBroadcastConnectionStateUpdate(state: NWConnection.State) {
switch state {
// shortened other cases since only logging occurs
case .ready:
logger.info("Broadcast connection established, ready to send and receive data.")
sendBroadcastMessage()
}
}
}
extension BroadcastDiscoveryEngine {
private func startBroadcastListener() {
broadcastResponseListener.stateUpdateHandler = handleBroadcastResponseListenerStateUpdate(state:)
broadcastResponseListener.newConnectionHandler = handleIncomingConnection(connection:)
broadcastResponseListener.start(queue: .global(qos: .default))
}
private func handleBroadcastResponseListenerStateUpdate(state: NWListener.State) {
switch state {
// shortened cases since only logging occurs
}
}
private func handleIncomingConnection(connection: NWConnection) {
connection.stateUpdateHandler = { state in self.handleIncomingConnectionStateUpdate(connection: connection, state: state) }
connection.start(queue: .global(qos: .default))
}
private func handleIncomingConnectionStateUpdate(connection: NWConnection, state: NWConnection.State) {
switch state {
// shortened other cases since only logging occurs
case .ready:
logger.info("Incoming connection (\(connection.debugDescription, privacy: .public) established, ready to send and receive data.")
connection.receiveMessage { content, contentContext, isComplete, error in
self.receiveBroadcastResponse(connection: connection, content: content, contentContext: contentContext, isComplete: isComplete, error: error)
}
}
}
private func receiveBroadcastResponse(connection: NWConnection, content: Data?, contentContext: NWConnection.ContentContext?, isComplete: Bool, error: NWError?) {
// shortened: handles parsing accordingly and then cancels connection
connection.cancel()
}
}