NWConnection receive - cancel?

Dabbling with Network.framework to see if I can replace some old socket based code on iOS. I was able to establish a TCP connection and send/receive data but I'm puzzled as to how receive timeouts should be handled: I can code my own timeouts using GCD but there doesn't seem to be a way to actually cancel the pending receive (it does seem to timeout by itself after a minute or so, tearing down the NWConnection with it - definitely not what I'm looking for).


Any suggestions?

Answered by DTS Engineer in 375421022

timeouts just mean the real Modbus device isn't responding.

Unless your user walks into a lift (per the example in my 31 Jul post), right?

Regardless, emulating your current approach using Network framework is still feasible. The basic idea would be for the receive side of your code to run continuously, that is, on open it would start a receive and then, when that receive completes, it would start another receive. When it receives a response, it would check to see if there’s someone waiting for that response. If so, it delivers the response to that waiter. Finally, the sender side of your code would register its interest with the receive side, do the send, and then wait for the response. From there, one of two things happens:

  • If the wait times out, it deregisters its interest and returns an error.

  • Otherwise, it deregisters its interest and then returns that response.

Sorting out all the details would be tricky [1] but it should work.

Then again, if you’re going to go to all this trouble it might be better spending your time switching to UDP, which better reflects the reality of your setup.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

[1] Especially the locking, because the send and receive sides must necessarily run on separate threads.

In your existing code, how do you respond to a receive timing out?

Most TCP code can’t do anything useful with the connection once it hits a timeout (because you’ve no idea whether the timeout was because the request never made it to the server or because the response never made it back) and thus the only thing you can do is abandon the connection. That’s easy to do in an async API like the Network framework. Eventually your cleanup code will get around to calling

cancel
, at which point your receive handler will be called with an
ECANCELED
error.

If that doesn’t work for you then I’ll need a better understanding of how your program handles timeouts to offer more suggestions.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you for the response.


In our legacy code, we would stop waiting for data off our socket after the expiry of the dispatch timer. I'm worried about multiple stale Network.framework receive handlers eating up resources and getting triggered later on when we get a response to a new send request. I might be approaching this the wrong way (our legacy code was synchronous but running on a background thread off an OperationQueue) but I don't see much point in adopting Network.framework if it will actually add to our code's complexity (the connection part is simpler but the lack of control over stale receive calls is somewhat troubling and may cause us headaches).

In our legacy code, we would stop waiting for data off our socket after the expiry of the dispatch timer. I'm worried about multiple stale Network.framework receive handlers eating up resources and getting triggered later on when we get a response to a new send request.

I’m still confused, alas. To be clear, TCP is a reliable protocol: If your request to read data off the connection times out, the data doesn’t go away. So, consider this sequence:

  1. You send request A.

  2. You attempt to read response A.

  3. At this point the user steps into a lift, and thus loses WWAN connectivity. Thus your read fails with a timeout.

  4. Shortly thereafter, the user steps out of the elevator and the data for response A arrives on your device. It sits in the connection buffer because your read request has already timed out.

  5. A few minutes later you send request B.

  6. You attempt to read response B. However, you get back response A because it’s ‘in front’ of response B in the TCP stream.

In a reliable protocol, like TCP, the only reasonable response to a timeout is to drop the connection. So, either I’m missing some subtlety of your architecture, in which case I’d love an in-depth explanation of that subtlety, or your previous code is doing something wrong.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Thank you again for the response.


Our legacy code is loosely using a TCP port over sockets and timeouts where no reply is received are semi-common (the IP messages are actually wrapped Modbus messages which are then sent over a RS485 link). In hindsight, using UDP datagrams might have made more sense but rewriting history isn't an option, sadly.


Therefore, if I understand correctly, opening a TCP NWConnection might not fit our needs at this point and we may have no choice but to rely on our legacy socket code.

Our legacy code is loosely using a TCP port over sockets and timeouts where no reply is received are semi-common (the IP messages are actually wrapped Modbus messages which are then sent over a RS485 link).

So you’re assuming that all timeouts are caused by failures on this remote bus rather than by delays at the networking level?

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

Yes, exactly. Due to the nature of the protocol (Modbus) wrapped into TCP (and the fact that we talk to multiple devices through 1 TCP-speaking 'hub' of some sort), timeouts just mean the real Modbus device isn't responding. I understand this is probably a specific behaviour on our end and Network.framework doesn't provide fine enough control to be a viable replacement to our old socket code at this time.

Accepted Answer

timeouts just mean the real Modbus device isn't responding.

Unless your user walks into a lift (per the example in my 31 Jul post), right?

Regardless, emulating your current approach using Network framework is still feasible. The basic idea would be for the receive side of your code to run continuously, that is, on open it would start a receive and then, when that receive completes, it would start another receive. When it receives a response, it would check to see if there’s someone waiting for that response. If so, it delivers the response to that waiter. Finally, the sender side of your code would register its interest with the receive side, do the send, and then wait for the response. From there, one of two things happens:

  • If the wait times out, it deregisters its interest and returns an error.

  • Otherwise, it deregisters its interest and then returns that response.

Sorting out all the details would be tricky [1] but it should work.

Then again, if you’re going to go to all this trouble it might be better spending your time switching to UDP, which better reflects the reality of your setup.

Share and Enjoy

Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware

let myEmail = "eskimo" + "1" + "@apple.com"

[1] Especially the locking, because the send and receive sides must necessarily run on separate threads.

Thank you for the feedback and suggestions, Quinn. I'll see if I can apply them to our specific use case.

NWConnection receive - cancel?
 
 
Q