I've just started working on an app that communicates with another device via tcp and I have some doubt.
1) Sometimes the device(server) and the users phone are connected to the same local network and sometimes they are not, when they are I need to use it's local address and when they aren't it's public. How do I handle those transitions ?(eg: when the user walks out of the wireless range)
Is that what the requiredLocalEndpoint is for? I couldn't make it out from the documentation.
2) How to use the betterPathUpdateHandler? I'm pretty sure the server does not support multipath tcp but I need constant communication with it, how do I handle changes between cellular and wireless connection?
My code so far is the following
class CrestronNetwork {
let endpoint = NWEndpoint.hostPort(host: “example.com", port: 11200)
let localEndpoint = NWEndpoint.hostPort(host: "192.168.20.15”, port: 11200)
let parameters = NWParameters.tcp
var connection: NWConnection!
let myQueue = DispatchQueue(label: "Network Queue")
var delegate: NetworkDelegate?
init() {
parameters.multipathServiceType = .disabled
parameters.expiredDNSBehavior = .allow
parameters.requiredLocalEndpoint = localEndpoint
connection = create(endpoint)
}
func create(_ myEndpoint:NWEndpoint) -> NWConnection {
let newConnection:NWConnection
newConnection = NWConnection(to: endpoint, using: parameters)
newConnection.stateUpdateHandler = {
(newState) in switch (newState) {
case .ready:
self.debug("ready")
self.connection = newConnection
self.sendMsg(message: "0-1")
self.receive(on: newConnection)
self.lookForBetterPath(on: newConnection
case .waiting(let error):
self.debugPrint(“Waiting error: \(error)")
case .failed(let error):
self.debugPrint(“Failed: \(error)")
self.start()
case .cancelled:
self.debugPrint(“Cancelled")
default:
break
}
}
newConnection.start(queue: self.myQueue)
return newConnection
}
func lookForBetterPath(on con:NWConnection) {
con.betterPathUpdateHandler = { (betterPathAvailable) in
if (betterPathAvailable) {
self.debugPrint("Better path available")
_ = self.start
}
}
}
func start() {
_ = create(endpoint)
}
func isReady() -> Bool {
if(connection.state == .ready) {
return true
}
return false
}
func sendMsg(message: String) {
let msg = message + "\r\n"
let data: Data? = msg.data(using: .utf8)
connection.send(content: data, completion: .contentProcessed { (sendError) in
if let sendError = sendError {
self.debugPrint(“\(sendError)")
}
})
}
func receive(on con: NWConnection) {
con.receive(minimumIncompleteLength: 1, maximumLength: 8192) { (content, context, isComplete, error) in
if let content = content {
DispatchQueue.main.async {
self.tellViewController(message: String(decoding: content, as: UTF8.self))
}
}
if con.state == .ready && isComplete == false {
self.receive(on: con)
}
}
}
func cancel() {
connection.cancel()
}
func tellViewController(message: String) {
DispatchQueue.main.async {
self.delegate?.passData(data: message)
}
}
}
I need to clarify your requirements, so let’s start with your first question:
Sometimes the device (server) and the users phone are connected to the same local network and sometimes they are not, when they are I need to use it’s local address and when they aren't it’s public
I’m presuming that the client is running on iOS and the server running on some non-Apple platform [1]. Correct?
In the above quote, which end does “it’s” refer to here?
Is that what the
is for?requiredLocalEndpoint
requiredLocalEndpoint
allows you to specify the local addressed used by a connection. It’s similar to the way that you might call
bind
before calling
connect
in BSD Sockets. However, there’s a subtlety that you need to understand. In BSD Sockets it’s traditional to bind to a specific local address on an outgoing TCP connection for two reasons:
To explicitly select a local address or port number or both (A)
To implicitly choose an interface (B).
requiredLocalEndpoint
is the equivalent to A but Network framework has better options for B [2]. Specifically, you can control the interface via the
requiredInterface
,
prohibitedInterfaces
,
requiredInterfaceType
and
prohibitedInterfaceTypes
properties on
NWParameters
.
Share and Enjoy
—
Quinn “The Eskimo!”
Apple Developer Relations, Developer Technical Support, Core OS/Hardware
let myEmail = "eskimo" + "1" + "@apple.com"
[1] It’s best to avoid the word device in contexts like this, because it’s common for folks to use device to refer their iOS device.
[2] Modern versions of BSD Sockets also support
IP_BOUND_IF
for explicit interface selection.