Reputation: 1770
I did ask same question on Apple dev portal and there is other people with the same problem.
I have created simple reproducible project on GitHub: (follow steps in README) https://github.com/ChoadPet/NWListenerTest.git
I have screen and on present ConnectionListener
is initialized
and on dismiss it deinitialized
(called stopListening()
).
First time when open the screen everything is ok:
Listener stateUpdateHandler: waiting(POSIXErrorCode: Network is down)
Listener stateUpdateHandler: ready
"📞 New connection: 10.0.1.2:50655 establish"
"Connection stateUpdateHandler: preparing"
"Connection stateUpdateHandler: ready"
but for next n-tries only this messages:
[] nw_path_evaluator_evaluate NECP_CLIENT_ACTION_ADD error [48: Address already in use]
[] nw_path_create_evaluator_for_listener nw_path_evaluator_evaluate failed
[] nw_listener_start_locked [L3] nw_path_create_evaluator_for_listener failed
Listener stateUpdateHandler: waiting(POSIXErrorCode: Network is down)
Listener stateUpdateHandler: failed(POSIXErrorCode: Address already in use)
It happens on iPhone 6 iOS 12.4.1, iPhone Xs Max iOS 13.3, iPhone 11 Pro iOS 13.5.1(also iOS 13.6)
but NOT on iPhone 7 Plus iOS 12.1.4, iPhone 11 iOS 13.5.1.
Here is my code for listening inbound connection:
final class ConnectionListener {
var dataReceivedHandler: ((Data) -> Void)?
private let port: UInt16
private let maxLength: Int
private var listener: NWListener!
private var connection: NWConnection!
init(port: UInt16, maxLength: Int) {
self.port = port
self.maxLength = maxLength
}
deinit {
print("❌ Deinitialize \(self)")
}
// MARK: Public API
func startListening() {
let parameters = NWParameters.tcp
parameters.allowLocalEndpointReuse = true
self.listener = try! NWListener(using: parameters, on: NWEndpoint.Port(integerLiteral: port))
self.listener.stateUpdateHandler = { state in print("Listener stateUpdateHandler: \(state)") }
self.listener.newConnectionHandler = { [weak self] in self?.establishNewConnection($0) }
self.listener.start(queue: .main)
}
func stopListening() {
listener.cancel()
connection?.cancel()
}
// MARK: Private API
private func establishNewConnection(_ newConnection: NWConnection) {
connection = newConnection
debugPrint("📞 New connection: \(String(describing: connection.endpoint)) establish")
connection.stateUpdateHandler = { [weak self] state in
guard let self = self else { return }
debugPrint("Connection stateUpdateHandler: \(state)")
switch state {
case .ready:
debugPrint("Connection: start receiving ✅")
self.receive(on: self.connection)
default: break
}
}
self.connection.start(queue: .main)
}
private func receive(on connection: NWConnection) {
connection.receive(minimumIncompleteLength: 1, maximumLength: maxLength, completion: { [weak self] content, context, isCompleted, error in
guard let self = self else { return }
if let frame = content {
self.dataReceivedHandler?(frame)
}
self.receive(on: connection)
})
}
}
If there is more information, which you need, let me know.
Thanks!
Upvotes: 6
Views: 1780
Reputation: 7278
After your app "fails", click STOP, wait 2 minutes, and try START again. I assure you, it will work normally again. I tried with your code, which also "failed" initially for me.
What's going on: you're running into a TCP close timeout. Imagine it as a postal service where you are the receiver. If the postal service says "we have stopped deliveries to you", you can remove whole mailbox immediately and there cannot be any delivery failures. This is an equivalent of THE CLIENT closing the TCP connection. But you tell the postal service that you accept deliveries, you actually have one already received, and you decide to move out. This is an equivalent of server closing the TCP connection. The mailbox (your iOS app process) is still there, you just don't want to receive anymore. In real world, you should ensure at least some more time of having your name on the mailbox, before everyone (in reasonable frame) notices. In TCP, this is called TIME_WAIT
.
TCP was not constructed primarily for the second scenario, but anyway it is trying to do its best to avoid lost packets, which just were delivered out of band (later than other packets) due to TCP routing. Particular wait length is specified in units of TCP stack parameters of the particular Operation System mark and version. So it may be shorter on some, and longer on others. It should not be more than 2 minutes.
Think over your use case. What are you trying to achieve by closing whole server, while the app is still running?
Upvotes: 1