Thanks Quinn for replying. Really appreciate you taking the time to write your response.
I have discovered a lot in the meantime.
- I now understand that are different broadcasting modes for UDP: Unicast, Broadcast en Multicast.
- TN3151: Choosing the right networking API - Explained:
Network framework supports UDP multicast using the NWConnectionGroup class, but that support has limits and, specifically, it does not support UDP broadcast. If you need something that’s not supported by Network framework, use BSD Sockets.
The game on console was using broadcast mode to broadcast data using UDP. I wrongfully assumed it was the same as multicast.
As has been mentioned in TN3151 Apple does not support UDP broadcast. Hopefully this will change in the future.
Having said that, coming back to your questions:
Are all the peers Apple devices? Or do you have a hardware accessory involved?
A game broadcasts data via UDP. So other than my app that is reading and visualizing that data to the user no Apple devices are involved.
What is role of each peer?
The role of each peer is just to receive the data being broadcasted by the game and processing it in different ways.
Is all traffic multicast? Or do you switch to unicast at some point?
Traffic is broadcast. I made a mistake. The game is able to switch between unicast and broadcast. Unicast works with the Network Framework, broadcast doesn't. Which is a shame, would be incredibly useful if it were.
Is this IPv4? Or IPv6? Or both?
This is IPv4.
What multicast address do you plan to use? And port?
The source (game) will broadcast the data to ALL machines on a local area network on a specific port.
And do you use the same port for both the source and destination?
Correct.
I was able to create code that made it work without the Network Framework.
class UDPBroadcastReceiver {
private var socketDescriptor: Int32 = -1
private let port: UInt16
private let queue = DispatchQueue(label: "UDPBroadcastReceiverQueue")
init(port: UInt16) {
self.port = port
setupSocket()
}
private func setupSocket() {
socketDescriptor = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP)
guard socketDescriptor > 0 else {
print("Failed to create socket")
return
}
var broadcastEnable: Int32 = 1
let broadcastEnableSize = socklen_t(MemoryLayout.size(ofValue: broadcastEnable))
setsockopt(socketDescriptor, SOL_SOCKET, SO_BROADCAST, &broadcastEnable, broadcastEnableSize)
var address = sockaddr_in(
sin_len: __uint8_t(MemoryLayout<sockaddr_in>.size),
sin_family: sa_family_t(AF_INET),
sin_port: in_port_t(port.bigEndian),
sin_addr: in_addr(s_addr: INADDR_ANY),
sin_zero: (0, 0, 0, 0, 0, 0, 0, 0)
)
let bindResult = withUnsafePointer(to: &address) {
$0.withMemoryRebound(to: sockaddr.self, capacity: 1) {
bind(socketDescriptor, $0, socklen_t(MemoryLayout<sockaddr_in>.size))
}
}
guard bindResult == 0 else {
print("Failed to bind socket")
close(socketDescriptor)
return
}
print("Socket setup and bound to port \(port)")
receive()
}
private func receive() {
queue.async {
var buffer = [UInt8](repeating: 0, count: 1024)
while true {
let bytesRead = recv(self.socketDescriptor, &buffer, buffer.count, 0)
if bytesRead > 0 {
let data = Data(buffer[0..<bytesRead])
// To Do: Parse data
} else if bytesRead == 0 {
print("Socket closed by peer")
break
} else {
print("Error receiving data: \(String(cString: strerror(errno)))")
break
}
}
}
}
deinit {
if socketDescriptor > 0 {
close(socketDescriptor)
}
}
}