vpoltave
vpoltave

Reputation: 1770

failed(POSIXErrorCode: Address already in use)

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

Answers (1)

Pavel Zdenek
Pavel Zdenek

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?

  • clients should close, not the server
  • server can cancel just single connections, not the whole listener
  • server can reject new connections while it's still running

Upvotes: 1

Related Questions