I have a question regarding how to set up a UDP listener on iOS 14. I have a UDP listener which has worked in the past, but after updating to iOS 14 it works sporadically/not at all.
This lives in an NSObject, and listens for a UDP broadcast across the local network on port 15000 (no specific IP address). It uses the CocoaAsyncSocket library. When I call setUpSocket() local network permissions are not triggered, but the app is able to sporadically pick up UDP packets.
var socket: GCDAsyncUdpSocket?
var broadcastPort: UInt16 = 15000
var broadcastAddress: String = ""
var connectAddress = ""
var connectPort = 0
func setUpSocket() {
findUDP()
let socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: DispatchQueue.main)
socket.setIPv4Enabled(true)
socket.setIPv6Enabled(false)
do {
try socket.bind(toPort: broadcastPort) /*15000*/
try socket.enableBroadcast(false)
try socket.beginReceiving()
} catch let error as NSError {
print("Issue with setting up listener \(error)")
}
}
/*Called when UDP packets are received.*/
func udpSocket(_ sock: GCDAsyncUdpSocket, didReceive data: Data, fromAddress: Data, withFilterContext filterContext: Any?) {
do {
let jsonDictionary = try JSONSerialization.jsonObject(with: data, options: []) as! [String : Any]
if (connected == false) {
if (jsonDictionary["Addresses"] != nil) {
if (jsonDictionary["Addresses"] is NSArray) {
let addresses = jsonDictionary["Addresses"] as! NSArray
for i in addresses {
let ipAddress:String = i as! String
if (ipAddress.range(of: "^([0-9]{1,3}\\.){3}[0-9]{1,3}(\\/([0-9]|[1-2][0-9]|3[0-2]))?$", options: .regularExpression) != nil) {
connectAddress = ipAddress
}
}
connectPort = jsonDictionary["Port"] as! Int
}
/*Sets up a TCP connection on the IP and Port provided in the UDP broadcast.*/
setupNetworkCommunication(ip: connectAddress, port: connectPort)
closeSocket()
}
}
} catch let error {
return print(error)
}
}
How can I update this to comply with iOS 14? If I need to update to use Bonjour services, how can I listen on a port without specifying an address (and without having to look for a specific Bonjour service broadcast, because the broadcast I'm looking for doesn't use Bonjour).
Is it acceptable to quickly open and close a Bonjour NWBrowser in order to trigger the network permissions, and then use my code as-is? This seems to work but seems hacky at best.
Thanks in advance.
Here are the steps that we had to go through in order to use CocoaAsyncSocket with UDP in our app:
Request the multicast entitlement from Apple (requestor must be the Account Holder of the team): com.apple.developer.networking.multicast
In the target's Info.plist, set a string for the following key:
Privacy - Local Network Usage Description
After getting permission for the entitlement from Apple, add a true(1) Boolean value for the following key in the *.entitlements file for your app (this last step is what kept us from receiving UDP broadcast packets):
com.apple.developer.networking.multicast
I was able to explore this some more and got some help via the apple developer forums, posting an answer here as well for those who are interested.
I ended up using an NWListener to listen for UDP packets, then set up an NWConnection once once I'd received something. I use this NWConnection to read data from the UDP broadcast.
From Quinn "The Eskimo:"
Listening for UDP broadcasts via an NWListener and then using the NWConnection objects it vends (via the new connection handler) to communicate over unicast with the broadcast’s sender is an expected use case.
I encourage anyone reading this to check out our discussion on the Apple Developer Forum as well.
Here is my implementation:
var udpListener: NWListener?
var udpConnection: NWConnection?
var backgroundQueueUdpListener = DispatchQueue.main
func findUDP() {
let params = NWParameters.udp
udpListener = try? NWListener(using: params, on: 15000)
udpListener?.service = NWListener.Service.init(type: "_appname._udp")
self.udpListener?.stateUpdateHandler = { update in
print("update")
print(update)
switch update {
case .failed:
print("failed")
default:
print("default update")
}
}
self.udpListener?.newConnectionHandler = { connection in
print("connection")
print(connection)
self.createConnection(connection: connection)
self.udpListener?.cancel()
}
udpListener?.start(queue: self.backgroundQueueUdpListener)
}
func createConnection(connection: NWConnection) {
self.udpConnection = connection
self.udpConnection?.stateUpdateHandler = { (newState) in
switch (newState) {
case .ready:
print("ready")
self.send()
self.receive()
case .setup:
print("setup")
case .cancelled:
print("cancelled")
case .preparing:
print("Preparing")
default:
print("waiting or failed")
}
}
self.udpConnection?.start(queue: .global())
}
func endConnection() {
self.udpConnection?.cancel()
}
Related
Context
I'm developing a Mac app. In this app, I want to run a websocket server. To do this, I'm using Swift NIO and Websocket-Kit. My full setup is below.
Question
All of the documentation for Websocket-Kit and SwiftNIO is geared towards a creating a single server-side process that starts up when you launch it from the command line and then runs infinitely.
In my app, I must be able to start the websocket server and then shut it down and restart it on demand, without re-launching my application. The code below does that, but I would like confirmation of two things:
In the test() function, I send some text to all connected clients. I am unsure if this is thread-safe and correct. Can I store the WebSocket instances as I'm doing here and message them from the main thread of my application?
Am I shutting down the websocket server correctly? The result of the call to serverBootstrap(group:)[...].bind(host:port:).wait() creates a Channel and then waits infinitely. When I call shutdownGracefully() on the associated EventLoopGroup, is that server cleaned up correctly? (I can confirm that port 5759 is free again after this shutdown, so I'm guessing everything is cleaned up?)
Thanks for the input; it's tough to find examples of using SwiftNIO and Websocket-Kit inside an application.
Code
import Foundation
import NIO
import NIOHTTP1
import NIOWebSocket
import WebSocketKit
#objc class WebsocketServer: NSObject
{
private var queue: DispatchQueue?
private var eventLoopGroup: MultiThreadedEventLoopGroup?
private var websocketClients: [WebSocket] = []
#objc func startServer()
{
queue = DispatchQueue.init(label: "socketServer")
queue?.async
{
let upgradePipelineHandler: (Channel, HTTPRequestHead) -> EventLoopFuture<Void> = { channel, req in
WebSocket.server(on: channel) { ws in
ws.send("You have connected to WebSocket")
DispatchQueue.main.async {
self.websocketClients.append(ws)
print("websocketClients after connection: \(self.websocketClients)")
}
ws.onText { ws, string in
print("received")
ws.send(string.trimmingCharacters(in: .whitespacesAndNewlines).reversed())
}
ws.onBinary { ws, buffer in
print(buffer)
}
ws.onClose.whenSuccess { value in
print("onClose")
DispatchQueue.main.async
{
self.websocketClients.removeAll { (socketToTest) -> Bool in
return socketToTest === ws
}
print("websocketClients after close: \(self.websocketClients)")
}
}
}
}
self.eventLoopGroup = MultiThreadedEventLoopGroup(numberOfThreads: 2)
let port: Int = 5759
let promise = self.eventLoopGroup!.next().makePromise(of: String.self)
let server = try? ServerBootstrap(group: self.eventLoopGroup!)
// Specify backlog and enable SO_REUSEADDR for the server itself
.serverChannelOption(ChannelOptions.backlog, value: 256)
.serverChannelOption(ChannelOptions.socketOption(.so_reuseaddr), value: 1)
.childChannelInitializer { channel in
let webSocket = NIOWebSocketServerUpgrader(
shouldUpgrade: { channel, req in
return channel.eventLoop.makeSucceededFuture([:])
},
upgradePipelineHandler: upgradePipelineHandler
)
return channel.pipeline.configureHTTPServerPipeline(
withServerUpgrade: (
upgraders: [webSocket],
completionHandler: { ctx in
// complete
})
)
}.bind(host: "0.0.0.0", port: port).wait()
_ = try! promise.futureResult.wait()
}
}
///
/// Send a message to connected clients, then shut down the server.
///
#objc func test()
{
self.websocketClients.forEach { (ws) in
ws.eventLoop.execute {
ws.send("This is a message being sent to all websockets.")
}
}
stopServer()
}
#objc func stopServer()
{
self.websocketClients.forEach { (ws) in
try? ws.eventLoop.submit { () -> Void in
print("closing websocket: \(ws)")
_ = ws.close()
}.wait() // Block until complete so we don't shut down the eventLoop before all clients get closed.
}
eventLoopGroup?.shutdownGracefully(queue: .main, { (error: Error?) in
print("Eventloop shutdown now complete.")
self.eventLoopGroup = nil
self.queue = nil
})
}
}
In the test() function, I send some text to all connected clients. I am unsure if this is thread-safe and correct. Can I store the WebSocket instances as I'm doing here and message them from the main thread of my application?
Exactly as you're doing here, yes, that should be safe. ws.eventLoop.execute will execute that block on the event loop thread belonging to that WebSocket connection. This will be safe.
When I call shutdownGracefully() on the associated EventLoopGroup, is that server cleaned up correctly? (I can confirm that port 5759 is free again after this shutdown, so I'm guessing everything is cleaned up?)
Yes. shutdownGracefully forces all connections and listening sockets closed.
I send messages to applications running on remote computers.
I send messages this ways.
c#:
void web_data_work()
{
try
{
TcpClient client = new TcpClient(address, port);
Byte[] data = Encoding.UTF8.GetBytes(string.Format("{0}", message));
NetworkStream stream = client.GetStream();
try
{
if (message != "")
{
stream.Write(data, 0, data.Length);
Byte[] readingData = new Byte[256];
String responseData = String.Empty;
StringBuilder completeMessage = new StringBuilder();
int numberOfBytesRead = 0;
do
{
numberOfBytesRead = stream.Read(readingData, 0, readingData.Length);
completeMessage.AppendFormat("{0}", Encoding.UTF8.GetString(readingData, 0, numberOfBytesRead));
}
while (stream.DataAvailable);
responseData = completeMessage.ToString();
this.Invoke((MethodInvoker)delegate ()
{
output_list.Items.Add(string.Format("Sended – {0}", responseData));
message = "";
});
}
}
finally
{
stream.Close();
client.Close();
}
}
catch
{
this.Invoke((MethodInvoker)delegate ()
{
output_list.Items.Add("Not sended");
message = "";
});
}
}
swift:
func web_data_work()
{
let address = address_box.stringValue
let port = port_box.intValue
let task = URLSession.shared.streamTask(withHostName: address, port: Int(port))
let data = message.data(using: .utf8)!
task.write(data as Data, timeout: 0)
{
error in
//om = "Not sended"
//self.output_message()
}
task.resume()
}
In c# i can read messages using TcpListener, how do i do it in swift?
Only two parameters are used:
"address" – ip address of the computer to which messages are sent and which is listened to by the TcpListener of that computer.
"port" – port of the sending and receiving between computers.
P.S. Preferably without additional libraries.
You can achieve this with just two classes: SocketPort & FileHandle.
Before you copy & paste the example below, please, read my comments at the end.
import Cocoa
class TcpEchoClient {
var readToEndOfFileCompletionHandler: (() -> Void)?
let fileHandle: FileHandle
var observer: NSObjectProtocol?
init(fileHandle: FileHandle) {
self.fileHandle = fileHandle
// Register observer for the data & EOF
self.observer = NotificationCenter.default.addObserver(forName: .NSFileHandleReadToEndOfFileCompletion,
object: fileHandle,
queue: OperationQueue.main) { [weak self] note in
self?.handleReadToEndOfFileCompletion(notification: note)
}
// Instruct the handle to read till the EOF & notify
fileHandle.readToEndOfFileInBackgroundAndNotify()
}
func handleReadToEndOfFileCompletion(notification note: Notification) {
defer {
// No matter what happens, call the completion handle by the end
readToEndOfFileCompletionHandler?()
}
// Is there an error?
if let errorCode = note.userInfo?["NSFileHandleError"] as? NSNumber {
print("Client \(fileHandle.fileDescriptor) error: File handle error \(errorCode.intValue)")
return
}
// No error, we should have data available
guard let data = note.userInfo?[NSFileHandleNotificationDataItem] as? Data else {
print("Client \(fileHandle.fileDescriptor) error: Unable to get data")
return
}
// Convert them to UTF-8 string
guard let text = String(data: data, encoding: .utf8) else {
print("Client \(fileHandle.fileDescriptor) error: Unable to convert data to UTF-8 string")
return
}
// Print the text
print("Client \(fileHandle.fileDescriptor) received: \(text)")
}
deinit {
// Remove observer for the data & EOF
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
// Close the handle
try? fileHandle.close()
}
}
class TcpServer {
let port: SocketPort
let fileHandle: FileHandle
var clients: Dictionary<Int32, TcpEchoClient>
var observer: NSObjectProtocol?
init?(tcpPort: UInt16) {
guard let socketPort = SocketPort(tcpPort: tcpPort) else {
return nil
}
// Keep the socket port around otherwise you'll get error 38
port = socketPort
// No clients for now
clients = [:]
// Create handle from the socket
fileHandle = FileHandle(fileDescriptor: port.socket)
// Register observer for the connection accepted
observer = NotificationCenter.default.addObserver(forName: .NSFileHandleConnectionAccepted,
object: fileHandle,
queue: OperationQueue.main) { [weak self] note in
if let handle = note.object as? FileHandle {
// Ask immediately for another accepted connection notification
handle.acceptConnectionInBackgroundAndNotify()
}
self?.handleConnectionAccepted(notification: note)
}
// Instruct the handle to accept connection & notify
fileHandle.acceptConnectionInBackgroundAndNotify()
}
func handleConnectionAccepted(notification note: Notification) {
// Is there an error?
if let errorCode = note.userInfo?["NSFileHandleError"] as? NSNumber {
print("Server error: File handle error \(errorCode.intValue)")
return
}
// No, we should have received the client file handle
guard let clientFileHandle = note.userInfo?[NSFileHandleNotificationFileHandleItem] as? FileHandle else {
print("Server error: Unable to get accepted connection file handle")
return
}
let fileDescriptor = clientFileHandle.fileDescriptor
// Create new client from the file handle
let client = TcpEchoClient(fileHandle: clientFileHandle)
// Once it finishes, remove it from the clients dictionary
client.readToEndOfFileCompletionHandler = { [weak self] in
guard let self = self else { return }
self.clients.removeValue(forKey: fileDescriptor)
print("Server: Client removed \(fileDescriptor) (total \(self.clients.count))")
}
// Store the client in the clients dictionary
clients[fileDescriptor] = client
print("Server: New client \(fileDescriptor) added (total \(self.clients.count))")
}
deinit {
// Remove all clients
clients.removeAll()
// Close the file handle
try? fileHandle.close()
// Invalidate the socket port
port.invalidate()
// Remove connection accepted observer
if let observer = observer {
NotificationCenter.default.removeObserver(observer)
}
}
}
Then create a property (var server: TcpServer?) and initialize it (server = TcpServer(tcpPort: 8080)).
Test:
parallel -j 1 echo -n Foo '|' nc 127.0.0.1 8080 ::: {1..4}
Console output:
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Server: New client 7 added (total 1)
Client 7 received: Foo
Server: Client removed 7 (total 0)
Comments:
This example is only a basic skeleton you can start with.
Don't be misleaded with the parallel test command.
I'm using it if I want to run multiple commands at once (same commands).
Everything in this example operates on the main queue.
Read documentation of all these classes, methods and notifications I do use. There're gotchas, you have to be familiar with run loops, etc.
I recently upgraded from AFNetworking 4 to 5.
This was the old way of initializing the listener:
let net = NetworkReachabilityManager()
net?.listener = { status in
if net?.isReachable ?? false {
switch status {
case .reachable(.ethernetOrWiFi):
print("The network is reachable over the WiFi connection")
case .reachable(.wwan):
print("The network is reachable over the WWAN connection")
case .notReachable:
print("The network is not reachable")
case .unknown :
print("It is unknown whether the network is reachable")
}
}
net?.startListening()
The new documentation reads this:
#discardableResult
open func startListening(onQueue queue: DispatchQueue = .main,
onUpdatePerforming listener: #escaping Listener) -> Bool
https://alamofire.github.io/Alamofire/Classes/NetworkReachabilityManager.html
In my code, I am trying this:
let listener = NetworkReachabilityManager.Listener()
self.reachabilityManager?.startListening(onUpdatePerforming: listener){
}
The compilation error I am getting is Extra argument 'onUpdatePerforming' in call. It is a syntactical issue, I am transitioning form Objective C to Swift.
What I attempt to pass a closure, I also cannot seem to get the syntax correct:
self.reachabilityManager?.startListening(onUpdatePerforming: { (NetworkReachabilityManager.Listener) in
})
Listener is just a typealias for the closure type expected, so you need to pass a closure.
self.reachabilityManager?.startListening { status in
switch status {
...
}
}
Here is the code which runs after the AFNetworking update:
self.reachabilityManager?.startListening(onUpdatePerforming: {networkStatusListener in
print("Network Status Changed:", networkStatusListener)
switch networkStatusListener {
case .notReachable:
self.presentAlert(message: "The network is not reachable. Please reconnect to continue using the app.")
print("The network is not reachable.")
case .unknown :
self.presentAlert(message: "It is unknown whether the network is reachable. Please reconnect.")
print("It is unknown whether the network is reachable.")
case .reachable(.ethernetOrWiFi):
print("The network is reachable over the WiFi connection")
case .reachable(.cellular):
print("The network is reachable over the WWAN connection")
}
})
I've been fighting with NWConnection to receive data on a long-running TCP socket all day. I've finally got it working after inflicting the following errors on myself due to lack of documentation:
Incomplete data (due to only calling receive once)
Getting TCP data out-of-order (due to "polling" receive from a timer...resulting in multiple simultaneous closures waiting to get data).
Suffering infinite loops (due to restarting receive after receiving without checking the "isComplete" Bool--once the socket is terminated from the other end this is....bad...very bad).
Summary of what I've learned:
Once you are in the .ready state you can call receive...once and only once
Once you receive some data, you can call receive again...but only if you are still in the .ready state and the isComplete is false.
Here's my code. I think this is right. But if it's wrong please let me know:
queue = DispatchQueue(label: "hostname", attributes: .concurrent)
let serverEndpoint = NWEndpoint.Host(hostname)
guard let portEndpoint = NWEndpoint.Port(rawValue: port) else { return nil }
connection = NWConnection(host: serverEndpoint, port: portEndpoint, using: .tcp)
connection.stateUpdateHandler = { [weak self] (newState) in
switch newState {
case .ready:
debugPrint("TcpReader.ready to send")
self?.receive()
case .failed(let error):
debugPrint("TcpReader.client failed with error \(error)")
case .setup:
debugPrint("TcpReader.setup")
case .waiting(_):
debugPrint("TcpReader.waiting")
case .preparing:
debugPrint("TcpReader.preparing")
case .cancelled:
debugPrint("TcpReader.cancelled")
}
}
func receive() {
connection.receive(minimumIncompleteLength: 1, maximumLength: 8192) { (content, context, isComplete, error) in
debugPrint("\(Date()) TcpReader: got a message \(String(describing: content?.count)) bytes")
if let content = content {
self.delegate.gotData(data: content, from: self.hostname, port: self.port)
}
if self.connection.state == .ready && isComplete == false {
self.receive()
}
}
}
I think you can use a short time connection many times. For example a client connects to the host and asks the host to do something and then tells the host to shutdown the connection. The host switches to the waiting mode to ready a new connection. See the diagram below.
You should have the connection timer to shutdown opened connection when the client don't send the close connection or answer event to the host for a particular time.
On a long-running TCP socket, you should implement customized heartbeat for monitor the connection status is alive or disconnected.
The heartbeat can as message or encrypt data to send, usually according to the server spec documents to implement.
Below as sample concept code to explain the flow for reference (without network packet content handler).
I can no guarantee is this common and correct way, but that's work for my project.
import Network
class NetworkService {
lazy var heartbeatTimeoutTask: DispatchWorkItem = {
return DispatchWorkItem { self.handleHeartbeatTimeOut() }
}()
lazy var connection: NWConnection = {
// Create the connection
let connection = NWConnection(host: "x.x.x.x", port: 1234, using: self.parames)
connection.stateUpdateHandler = self.listenStateUpdate(to:)
return connection
}()
lazy var parames: NWParameters = {
let parames = NWParameters(tls: nil, tcp: self.tcpOptions)
if let isOption = parames.defaultProtocolStack.internetProtocol as? NWProtocolIP.Options {
isOption.version = .v4
}
parames.preferNoProxies = true
parames.expiredDNSBehavior = .allow
parames.multipathServiceType = .interactive
parames.serviceClass = .background
return parames
}()
lazy var tcpOptions: NWProtocolTCP.Options = {
let options = NWProtocolTCP.Options()
options.enableFastOpen = true // Enable TCP Fast Open (TFO)
options.connectionTimeout = 5 // connection timed out
return options
}()
let queue = DispatchQueue(label: "hostname", attributes: .concurrent)
private func listenStateUpdate(to state: NWConnection.State) {
// Set the state update handler
switch state {
case .setup:
// init state
debugPrint("The connection has been initialized but not started.")
case .waiting(let error):
debugPrint("The connection is waiting for a network path change with: \(error)")
self.disconnect()
case .preparing:
debugPrint("The connection in the process of being established.")
case .ready:
// Handle connection established
// this means that the handshake is finished
debugPrint("The connection is established, and ready to send and receive data.")
self.receiveData()
self.sendHeartbeat()
case .failed(let error):
debugPrint("The connection has disconnected or encountered an: \(error)")
self.disconnect()
case .cancelled:
debugPrint("The connection has been canceled.")
default:
break
}
}
// MARK: - Socket I/O
func connect() {
// Start the connection
self.connection.start(queue: self.queue)
}
func disconnect() {
// Stop the connection
self.connection.stateUpdateHandler = nil
self.connection.cancel()
}
private func sendPacket() {
var packet: Data? // do something for heartbeat packet
self.connection.send(content: packet, completion: .contentProcessed({ (error) in
if let err = error {
// Handle error in sending
debugPrint("encounter an error with: \(err) after send Packet")
} else {
// Send has been processed
}
}))
}
private func receiveData() {
self.connection.receive(minimumIncompleteLength: 1, maximumLength: 8192) { [weak self] (data, context, isComplete, error) in
guard let weakSelf = self else { return }
if weakSelf.connection.state == .ready && isComplete == false, var data = data, !data.isEmpty {
// do something for detect heart packet
weakSelf.parseHeartBeat(&data)
}
}
}
// MARK: - Heartbeat
private func sendHeartbeat() {
// sendHeartbeatPacket
self.sendPacket()
// trigger timeout mission if the server no response corresponding packet within 5 second
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 5.0, execute: self.heartbeatTimeoutTask)
}
private func handleHeartbeatTimeOut() {
// this's sample time out mission, you can customize this chunk
self.heartbeatTimeoutTask.cancel()
self.disconnect()
}
private func parseHeartBeat(_ heartbeatData: inout Data) {
// do something for parse heartbeat
// cancel heartbeat timeout after parse packet success
self.heartbeatTimeoutTask.cancel()
// send heartbeat for monitor server after computing 15 second
DispatchQueue.global(qos: .background).asyncAfter(deadline: .now() + 15.0) {
self.sendHeartbeat()
}
}
}
currently I am using GCDAsyncUdpSocket to send a udp broadcast. Currently, the code sends it to a hardcoded 255.255.255.255 address. Is that the correct broadcast address on all networks? Will it fail on certain networks? What is the correct swift code?
socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: DispatchQueue.main)
socket!.send(ipRequestData!, toHost: "255.255.255.255", port: discoveryPort, withTimeout: 10000, tag: 0)
This question has an answer but it is in Objective-C
Calculating the Broadcast Address in Objective-C
Your broadcast address depends on your host address and the subnet mask. You can find the formula to calculate the broadcast address on Wikipedia:
broadcastAddress = ipAddress | ~subnetMask
You can calculate it with the following function:
func calculateBroadcastAddress(ipAddress: String, subnetMask: String) -> String {
let ipAdressArray = ipAddress.split(separator: ".")
let subnetMaskArray = subnetMask.split(separator: ".")
guard ipAdressArray.count == 4 && subnetMaskArray.count == 4 else {
return "255.255.255.255"
}
var broadcastAddressArray = [String]()
for i in 0..<4 {
let ipAddressByte = UInt8(ipAdressArray[i]) ?? 0
let subnetMaskbyte = UInt8(subnetMaskArray[i]) ?? 0
let broadcastAddressByte = ipAddressByte | ~subnetMaskbyte
broadcastAddressArray.append(String(broadcastAddressByte))
}
return broadcastAddressArray.joined(separator: ".")
}
Example:
let broadcastAddress = calculateBroadcastAddress(ipAddress: "172.16.0.0", subnetMask: "255.240.0.0")
Result:
"172.31.255.255"
In general, you should never choose the main queue for network operations because it will lead to lags in the user interface (which is drawn in the main queue). Better use
let udpSocket = GCDAsyncUdpSocket(delegate: self, delegateQueue: DispatchQueue.global())
Do not forget to explicitly enable broadcasts for this socket
do {
try udpSocket?.enableBroadcast(true)
} catch let error {
print(error)
}
Also consider that Apple enforces an iOS app to work with IPv6, which does not support broadcasts but multicasts. So maybe you should switch to multicasts at least if your device is inside a pure IPv6 network.