Our App design requires Apps to be able to discover and communicate with other Apps in the same Local/Enterprise network over a range of given ports.
To facilitate this, I am exploring NWConnectionGroup over UDP.
As per the following documentation (How to use multicast networking in your app) I am writing sample code where objective is to create a Multicast group among two different programs running on the same macOS (14.1) system and enabling multicast communication between them.
Based on the following note in documentation I understand that I don’t need multicast entitlement if I am using sample code (Command Line Tool).
Note: You can test your app using the iOS and iPadOS simulators without an active entitlement, but using multicast and broadcast networking on physical hardware requires the entitlement.
Is this understanding correct? If yes.
Approach 1:
When I create multicast group in both the programs on same multicast IP and Port (224.0.0.251:8900), the second program is not executing and gives following "Address already in use" error.
I have used allowLocalEndpointReuse (true) while creating NWConnectionGroup.
Is this expected?
Error Logs:
Group entered state failed(POSIXErrorCode(rawValue: 48): Address already in use)
Send complete with error Optional(POSIXErrorCode(rawValue: 89): Operation canceled)
nw_path_evaluator_evaluate NECP_CLIENT_ACTION_ADD error 48
nw_path_create_evaluator_for_listener nw_path_evaluator_evaluate failed
nw_listener_start_on_queue [L1] nw_path_create_evaluator_for_listener failed
nw_connection_group_handle_listener_state_change [G1] listener failed with error
nw_connection_group_send_message_internal [G1] Cannot send a message on a failed or cancelled group
As per attached sample code, do you see anything specific which I might be missing, pointers to that will help.
Note: Difference between Approach1 and Approach2 is only the Port Number
Second Approach:
So, I tried with same multicast IP but binding to different ports for both the programs (224.0.0.251:8900 & 224.0.0.251:8901). Here, for both the programs group.send() to self was getting received by the group.setReceiveHandler() but same multicast message was not being received by the other program.
Question: Is this approach correct? If yes, what could be the reason behind NOT receiving the multicast messages?
/************** NWConnectionGroup 1*********************/
import Foundation
import Network
// Create a NWMulticastGroup to describe the multicast group
guard let multicast = try? NWMulticastGroup(for: [.hostPort(host: "224.0.0.250", port: 8900)]) else {
fatalError("Error creating multicast group")
}
let params = NWParameters.udp;
params.allowLocalEndpointReuse = true;
// Create an NWConnectionGroup to handle multicast communication
let group = NWConnectionGroup(with: multicast, using: params)
// Set the handler for incoming messages
group.setReceiveHandler(maximumMessageSize: 16384, rejectOversizedMessages: true) { (message, content, isComplete) in
print("Received message from \(String(describing: message.remoteEndpoint))")
if let content = content, let message = String(data: content, encoding: .utf8) {
print("Received Message: \(message)")}
//print("Current group members:", group.descriptor.members)
for member in group.descriptor.members {
switch member {
case .hostPort(let host, let port):
print("Member Host: \(host), Port: \(port)")
default:
print("Member Endpoint: \(member)")
}
}
// Send acknowledgment
//let sendContent = Data("ack from Device 1".utf8)
/*group.send(content: sendContent) { (error) in
print("Acknowledgment sent with error: \(String(describing: error))")
}*/
if isComplete {
print("Connection closed")
}
}
// Set the state update handler
group.stateUpdateHandler = { (newState) in
print("Group entered state \(String(describing: newState))")
}
// Start the NWConnectionGroup
group.start(queue: .main)
// Send a message to the multicast group
let groupSendContent = Data("Hello All from Device 1".utf8)
group.send(content: groupSendContent) { (error) in
print("Send complete with error \(String(describing: error))")
}
// Keep the run loop running
RunLoop.main.run()
/***************************NWConnectionGroup 2 **********************/
import Foundation
import Network
// Create a NWMulticastGroup to describe the multicast group
guard let multicast = try? NWMulticastGroup(for: [.hostPort(host: "224.0.0.250", port: 8901)]) else {
fatalError("Error creating multicast group")
}
let params = NWParameters.udp;
params.allowLocalEndpointReuse = true;
// Create an NWConnectionGroup to handle multicast communication
let group = NWConnectionGroup(with: multicast, using: params)
// Set the handler for incoming messages
group.setReceiveHandler(maximumMessageSize: 16384, rejectOversizedMessages: true) { (message, content, isComplete) in
print("Received message from \(String(describing: message.remoteEndpoint))")
if let content = content, let message = String(data: content, encoding: .utf8) {
print("Received Message: \(message)")}
// Send acknowledgment
/*let sendContent = Data("ACK from Device 2".utf8)
group.send(content: sendContent, to: message.remoteEndpoint) { (error) in
print("Acknowledgment sent with error: \(String(describing: error))")
}*/
if isComplete {
print("Connection closed")
}
}
// Set the state update handler
group.stateUpdateHandler = { (newState) in
print("Group entered state \(String(describing: newState))")
}
// Start the NWConnectionGroup
group.start(queue: .main)
// Send a message to the multicast group
let groupSendContent = Data("Hello All from Device 2".utf8)
group.send(content: groupSendContent) { (error) in
print("Send complete with error \(String(describing: error))")
}
// Keep the run loop running
RunLoop.main.run()
Post
Replies
Boosts
Views
Activity
During my exploration of NWConnection and NWListener I created sample code to implement communication between client and listener over UDP. To achieve that client and listener were created on the same machine using localhost. Same client and listener codes were also deployed on different macOS machines. Following are the observations:
Observation1:
Within the same macOS machine, Listener was created on a port X and from client side I used "localhost" and Port X to connect to the listener.
To capture the behavior of the how the connections/FDs are getting created I used lsof & netstat commands and following were the output:
LSOF:
tw-macoffice-02-studio:~ nikhil.singh$ lsof -Pi :9001
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
NWListner 16775 nikhil.singh 4u IPv6 0x402b8381d3a12129 0t0 UDP *:9001
NWListner 16775 nikhil.singh 5u IPv6 0x402b8381d3a11129 0t0 UDP localhost:9001->localhost:64723
NWConnect 16785 nikhil.singh 4u IPv4 0x402b8381d3a10529 0t0 UDP localhost:64723->localhost:9001
Here, we can see that separate FDs are getting created for:
NWListner - Listener FD - 4u and NWConnection (connection request) from listener to client FD - 5u on localhost with Process ID 16775
NWConnect - NWConnection from client to listener on localhost FD - 4u Process ID 16785
NETSTAT:
tw-macoffice-02-studio:~ nikhil.singh$ netstat -an |grep 9001
udp4 0 0 127.0.0.1.9001 127.0.0.1.64723
udp4 0 0 127.0.0.1.64723 127.0.0.1.9001
udp46 0 0 *.9001 .
Connection wise also we can see the two connections are listed.
Observation 2:
I used different macOS machines within the same network to implement listener and client side of the code. Here, the behavior in terms of socket FD creation on the listener side with incoming connection request was different.
Listener Side:
LSOF:
nikhil.singh@Mac-Pro ~ % lsof -Pi :9001
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
UDP_Serve 31480 nikhil.singh 5u IPv6 0xb3b7cb5ed6d2edd7 0t0 UDP *:9001
NETSTAT:
nikhil.singh@Mac-Pro ~ % netstat -an |grep 9001
udp46 0 0 *.9001 .
udp4 0 0 10.20.16.144.9001 10.20.16.250.62758
Inconsistency:
**For listener side, a new socket FD was not created when listener accepted the incoming connection. **
Client Side:
LSOF
tw-macoffice-02-studio:~ nikhil.singh$ lsof -Pi :9001
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
NWConnect 29015 nikhil.singh 4u IPv4 0x402b8381d3c74129 0t0 UDP 10.20.16.250:62758->tw-macoffice-01.tallysolutions.com:9001
NETSTAT
tw-macoffice-02-studio:~ nikhil.singh$ netstat -an |grep 9001
udp4 0 0 10.20.16.250.62758 10.20.16.144.9001
For Client Side, a new FD was created from client to listener.
Question:
Why a new socket FD was not created on the Listener side when incoming connection was from a different machine over IP.
Also, one Digression question:
As a cross platform application, we plan to use Network Framework for Apple Kernel and BSD sockets for others. Similar to BSD, is there a way we can block a thread on connection.receiveMessage?