Problems setting up server using Network.framework

I’m pulling out my hair trying to reverse-engineer the more-or-less undocumented Network framework. It appears that two things are happening, one is that the listener is accepting two connection requests, one on ipv4 and one on ipv6. Secondly, it seems that the the connection immediately sets isComplete.

Also, what does prohibit joining mean in the connection?
server: did accept connection: [C1 ::1.63423 tcp, local: ::1.63406, server, prohibit joining, path satisfied (Path is satisfied), interface: lo0]

It sure would be nice to have more examples of this framework in action, it feels like Apple is not really committed to it and that worries me.


Code Block
class Server {
var publisher = PassthroughSubject<Data, NWError>()
private var connection: NWConnection? = nil
let listener: NWListener
init() {
listener = try! NWListener(using: .tcp)
}
func report(_ msg: String) {
print("server: \(msg)")
}
func start() throws {
report("start server")
listener.service = NWListener.Service(name: bonjourName, type: bonjourService, domain: nil)
listener.stateUpdateHandler = self.stateDidChange(to:)
listener.newConnectionHandler = self.didAccept(nwConnection:)
listener.serviceRegistrationUpdateHandler = self.serviceRegistrationUpdateHandler
listener.start(queue: .main)
}
private func stateDidChange(to newState: NWListener.State) {
switch newState {
case .ready:
report("Server ready service=\(listener.service) port=\(listener.port)")
case .failed(let error):
report("Server failure, error: \(error.localizedDescription)")
publisher.send(completion: Subscribers.Completion.failure(error))
default:
break
}
}
private func serviceRegistrationUpdateHandler(_ change: NWListener.ServiceRegistrationChange) {
report("registration did change: \(change)")
}
private func didAccept(nwConnection: NWConnection) {
report("did accept connection: \(nwConnection)")
connection = nwConnection
connection?.stateUpdateHandler = self.stateDidChange
connection?.start(queue: .main)
receive()
}
private func stateDidChange(to state: NWConnection.State) {
switch state {
case .waiting(let error):
publisher.send(completion: Subscribers.Completion.failure(error))
case .ready:
report("connection ready")
case .failed(let error):
publisher.send(completion: Subscribers.Completion.failure(error))
connectionDidFail(error: error)
default:
break
}
}
// functions removed to make space
private func receive() {
connection?.receive(minimumIncompleteLength: 1, maximumLength: Int.max) { (data, _, isComplete, error) in
if let error = error {
self.report("connection failed: \(error)")
self.connectionDidFail(error: error)
return
}
if let data = data, !data.isEmpty {
self.report("connection did receive, data: \(data)")
self.publisher.send(data)
}
if isComplete {
self.report("is complete")
// self.connectionDidEnd()
return
} else {
self.report("setup next read")
self.receive()
}
}
}
func send(data: Data) {
report("connection will send data: \(data)")
self.connection?.send(content: data, contentContext: .defaultStream, completion: .contentProcessed( { error in
if let error = error {
self.connectionDidFail(error: error)
return
}
self.report("connection did send, data: \(data)")
}))
}
func sendStreamOriented(connection: NWConnection, data: Data) {
connection.send(content: data, completion: .contentProcessed({ error in
if let error = error {
self.connectionDidFail(error: error)
}
}))
}
}

It sure would be nice to have more examples of this framework in action, it feels like Apple is not really committed to it and that worries me.

One excellent example to take a look at is Building a Custom Peer-to-Peer Protocol using a game called Tic-Tac-Toe. This uses all of the components you have in your code; Bonjour, NWListener, and NWConnection.

It appears that two things are happening, one is that the listener is accepting two connection requests, one on ipv4 and one on ipv6.

As for the issues you are seeing, I was able to reproduce what you are seeing as well. I am seeing two IPv6 addresses as well with link local addresses prefixes fe80::1 *. The local one should be scoped to the interface you are using. I am suspecting the C1 then routes to remote.

As far as your listener accepting two connections, this may be due to the logic between your listener and your browser. Check that newConnectionHandler is not receiving multiple connections and therefore you are setting them up. Also, in my case, when NWBrowser found the Bonjour endpoint and the new connection was created I immediately canceled the browser and this eliminated the issue.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Also, I would recommend decoupling your NWConnection, NWListener, and NWBrowser logic as much as possible.

Sample listener:
Code Block swift
import Foundation
import Network
import os
protocol NetworkListenerDelegate: AnyObject {
func didReceiveListenerUpdate(listener: NetworkListener, didUpdateMessage: String)
func didReceiveNewConnection(listener: NetworkListener, newConnection: NWConnection)
}
class NetworkListener {
private var networkListener: NWListener?
private var didListenerFail: Bool = false
private var didConnectionSetup: Bool = false
public static let shared = NetworkListener()
public weak var networkDelegate: NetworkListenerDelegate?
func startListener() {
do {
let tcpOption = NWProtocolTCP.Options()
tcpOption.enableKeepalive = true
tcpOption.keepaliveIdle = 2
let params = NWParameters(tls: nil, tcp: tcpOption) /* Configure TLS here */
params.includePeerToPeer = true
let listener = try NWListener(using: params)
networkListener = listener
listener.service = NWListener.Service(name: NetworkConstants.serviceName,
type: NetworkConstants.serviceType)
listener.stateUpdateHandler = { [weak self] newState in
guard let strongSelf = self else { return }
switch newState {
case .ready:
let listenerMessage = "Listener - listening on \(NetworkConstants.serviceName) \(NetworkConstants.serviceType)"
strongSelf.networkDelegate?.didReceiveListenerUpdate(listener: strongSelf, didUpdateMessage: listenerMessage)
case .failed(let error):
strongSelf.networkListener?.cancel()
if strongSelf.didListenerFail {
strongSelf.didListenerFail = true
NetworkListener.shared.startListener()
} else {
let errorMessage = "Listener - failed with \(error.localizedDescription), restarting"
strongSelf.networkDelegate?.didReceiveListenerUpdate(listener: strongSelf, didUpdateMessage: errorMessage)
}
default:
break
}
}
listener.newConnectionHandler = { [weak self] newConnection in
guard let strongSelf = self else { return }
if strongSelf.didConnectionSetup {
return
}
strongSelf.didConnectionSetup = true
let listenerMessage = "Listener received a new connection"
strongSelf.networkDelegate?.didReceiveListenerUpdate(listener: strongSelf, didUpdateMessage: listenerMessage)
strongSelf.networkDelegate?.didReceiveNewConnection(listener: strongSelf, newConnection: newConnection)
}
listener.start(queue: .main)
} catch {
os_log("Failed to create listener: %{PUBLIC}@", error.localizedDescription)
}
}
func cancelListening() {
networkListener?.cancel()
}
}


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com 
Sample connection:
Code Block swift
import Foundation
import Network
protocol NetworkConnectionDelegate: AnyObject {
func didEstablishConnection(connection: NetworkConnection, with error: Error?)
func didReceivedData(connection: NetworkConnection, with messageResponse: ConnectionMessage)
}
class NetworkConnection {
public var connection: NWConnection?
private let encoder = JSONEncoder()
private let decoder = JSONDecoder()
weak var delegate: NetworkConnectionDelegate?
init(newConnection: NWConnection) {
connection = newConnection
}
func startConnection() {
connection?.stateUpdateHandler = { [weak self] newState in
guard let strongSelf = self else { return }
switch newState {
case .ready:
strongSelf.receiveIncomingDataOnConnection()
print("Connection established")
strongSelf.delegate?.didEstablishConnection(connection: strongSelf, with: nil)
case .preparing:
print("Connection preparing")
case .setup:
print("Connection setup")
case .waiting(let error):
print("Connection waiting: \(error.localizedDescription)")
case .failed(let error):
print("Connection failed: \(error.localizedDescription)")
strongSelf.connection?.cancel()
strongSelf.delegate?.didEstablishConnection(connection: strongSelf, with: error)
default:
break
}
}
connection?.start(queue: .main)
}
func sendDataOnConnection(connectionMessage: ConnectionMessage) {
do {
let data = try encoder.encode(connectionMessage)
connection?.send(content: data, completion: .contentProcessed { error in
if let error = error {
print("Error sending data: \(error.localizedDescription)")
} else {
print("\(data.count) bytes sent")
}
})
} catch {
print("Error sending data: \(error.localizedDescription)")
}
}
func receiveIncomingDataOnConnection() {
connection?.receive(minimumIncompleteLength: 1, maximumLength: 65535) { [weak self] (content, context, isComplete, error) in
guard let strongSelf = self else { return }
if let messageData = content {
do {
let messageResponse = try strongSelf.decoder.decode(ConnectionMessage.self, from: messageData)
strongSelf.delegate?.didReceivedData(connection: strongSelf, with: messageResponse)
strongSelf.receiveIncomingDataOnConnection()
} catch {
print("Error decoding data: \(error.localizedDescription)")
}
}
if isComplete, content?.count == 0 {
strongSelf.receiveIncomingDataOnConnection()
}
}
}
func cancel() {
connection?.cancel()
}
}


Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com 
I'll take a look at your code. I was definitely receiving and accepting more than one connection, I just wasn't clear on why until I fired up Wireshark. In my case both instances (sender and receiver) were running on the same node in two different simulators so I assumed that everything would be flowing through lo0. Although my example just handles a single connection I would need to generalize this and therefore was thinking that the listen would stay active in case the client (there is only one) restarted. I'll check out your code, thanks again!
Hi Matt, this is working for me, thank you. I have a follow-up question. I'm sending large messages which can't be sent in one chunk (large images encoded as Data). The receive loop from your example picks up all the fragments according to content.count but it isn't clear when the message is complete to assemble and decode. The doc says that isComplete marks the end of a logical message, but possibly not for plain data. How do I know when I've gotten all the pieces?

Alternatively, is there an interface which does the work for me and returns the entire chunk of data?

I'll ask another way, is there some other API I should be using to pass images peer-to-peer in the LAN?


I have a follow-up question.

It’s probably best to put your follow-up question in a new thread. Feel free to reference this thread if you think any of the above is critical to understanding this second issue.

Make sure to use the Network tag so that the relevant folks see it.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@apple.com"
I missed you previous response. Yes, as Quinn mentioned, if you continue to have problems with your data transmission and are not able to work this out please post another question that relates to this question with updated code in the Network forums.

Matt Eaton
DTS Engineering, CoreOS
meaton3@apple.com
Problems setting up server using Network.framework
 
 
Q