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()
}