I'm attempting to build an app the broadcasts on the local network and awaits responses to those broadcast requests. However, the app does not receive the responses. Essentially, the app broadcasts "DISCOVER:1000" to 10.11.21.255, and expects "ADDRESS:10.11.21.100", but never receives it. I built a test client in python, and it works as expected. Running tcpdump on the server shows the response being sent by the server. It just never reaches the ios app.
In case it matters, I have the multicast entitlement for the app and local network enabled in Info.plist.
import Foundation
import Network
class UDPClient: ObservableObject {
private var connection: NWConnection?
private let networkQueue = DispatchQueue(label: "com.example.udp")
@Published var isReady = false
private var isListening = false
func connect() {
let host = "10.11.21.255"
let port = UInt16(12345)
let endpoint = NWEndpoint.hostPort(host: NWEndpoint.Host(host), port: NWEndpoint.Port(integerLiteral: port))
connection = NWConnection(to: endpoint, using: .udp)
connection?.stateUpdateHandler = { [weak self] state in
switch state {
case .ready:
self?.networkQueue.async {
print("Connection ready")
self?.startReceiving()
DispatchQueue.main.async {
self?.isReady = true
}
}
case .failed(let error):
print("Connection failed: \(error)")
DispatchQueue.main.async {
self?.isReady = false
}
case .waiting(let error):
print("Connection waiting: \(error)")
DispatchQueue.main.async {
self?.isReady = false
}
default:
DispatchQueue.main.async {
self?.isReady = false
}
break
}
}
connection?.start(queue: networkQueue)
}
func send() {
let message = "DISCOVER:1000"
guard let data = message.data(using: .utf8) else {
print("Failed to convert message to data")
return
}
guard let connection = self.connection else {
return
}
networkQueue.async { [weak self] in
print("Attempting to send message...")
guard let self = self else { return }
if self.isReady {
// Ensure we're listening before sending
if !self.isListening {
self.startReceiving()
// Add a small delay to ensure the receiver is ready
self.networkQueue.asyncAfter(deadline: .now() + 0.1) {
self.performSend(data: data, connection: connection)
}
} else {
self.performSend(data: data, connection: connection)
}
} else {
print("Connection is not ready. Retrying in 100ms...")
self.networkQueue.asyncAfter(deadline: .now() + 0.1) {
self.send()
}
}
}
}
private func performSend(data: Data, connection: NWConnection) {
connection.send(content: data, completion: .contentProcessed { error in
if let error = error {
print("Failed to send: \(error)")
} else {
print("Send completed successfully")
}
})
}
private func startReceiving() {
print("Starting to receive messages...")
isListening = true
connection?.receiveMessage { [weak self] content, context, isComplete, error in
guard let self = self else { return }
if let error = error {
print("Receive error: \(error)")
return
}
if let data = content {
print("Received data: \(data)")
if let responseString = String(data: data, encoding: .utf8) {
print("Received response: \(responseString)")
} else {
print("Received data could not be converted to string.")
}
} else {
print("No data received.")
}
// Continue receiving
self.startReceiving()
}
}
func disconnect() {
networkQueue.async { [weak self] in
self?.connection?.cancel()
self?.isListening = false
DispatchQueue.main.async {
self?.isReady = false
}
print("Disconnected")
}
}
}
My main view:
import SwiftUI
struct ContentView: View {
@StateObject private var udpClient = UDPClient()
var body: some View {
VStack {
Image(systemName: "globe")
.imageScale(.large)
.foregroundStyle(.tint)
Text("Hello, world!")
}
.padding()
.onAppear() {
udpClient.connect()
udpClient.send()
}
}
}
#Preview {
ContentView()
}
Network framework’s support for UDP broadcasts is quite limited. This is something I call out in TN3151 Choosing the right networking API.
It’s best to think of an NWConnection
like a connected UDP socket. It’s really intended to be used to transfer a UDP flow, that is, a series of datagrams between the same local IP / local port / remote IP / remote port tuple. If your network protocol needs to do something else, it’s unlikely that NWConnection
will help.
The standard fallback is BSD Sockets.
However, before you go down that path I’d want to check whether you really need to be operating at this level. It looks like you’re implementing a service discovery protocol, and if that’s the case then Bonjour is a much better option. I talk about this in Extra-ordinary Networking > Don’t Try to Get the Device’s IP Address > Service Discovery.
Share and Enjoy
—
Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"