Posts

Post marked as solved
3 Replies
2.4k Views
I'm using tcp sockets that by default will try to connect through a hostname, but if the local network does not have WAN access and cannot resolve the hostname it should attempt to connect through a local ip instead. How do I go about doing this? I've noticed that the connection will get stuck on preparing and will not timeout in a timely manner.alternativeEndpoint is suposed to be this fallback local ip while the normal endpoint is a public hostname, the debugger is just logging strings and keeping track of connection states, networkStatusChanged will block the ui with a spinner whenever online is falseimport UIKit import Network import Foundation @objc protocol NetworkDelegate: class { func passData(data: String) // sends received data to the delegate func networkStatusChanged(online: Bool, connectivityStatus: String) @objc optional func viabilityChanged(isViable: Bool) } struct Connection { let connection: NWConnection let id: Int } class TCPConnection { var endpoint: NWEndpoint var alternativeEndpoint: NWEndpoint? let parameters = NWParameters.tcp var connection: NWConnection! var connections: [Connection] = [] let myQueue = DispatchQueue(label: "Network Queue") weak var delegate: NetworkDelegate? weak var debuggerDelegate: NetworkDebugging? var online = false private static var nextID: Int = 0 var activeConnectionID = 0 private var bestConnectionID: Int = 0 private var retryTimer: Timer? private var retryAttempts = 0 private var maxRetryAttempts = 3 init(endpoint: NWEndpoint, alternativeEndpoint: NWEndpoint) { self.endpoint = endpoint self.alternativeEndpoint = alternativeEndpoint commonInit() } init(endpoint: NWEndpoint) { self.endpoint = endpoint commonInit() } func commonInit() { parameters.multipathServiceType = .disabled parameters.expiredDNSBehavior = .allow connection = create(endpoint) lookForPathUpdate(on: connection) lookForBetterPath(on: connection) lookForConnectivity(on: connection) } func start() { _ = create(endpoint) } func create(_ myEndpoint:NWEndpoint) -> NWConnection { let currentID = TCPConnection.nextID debugPrint("Creating connection to \(myEndpoint)") let newConnection = NWConnection(to: myEndpoint, using: parameters) connections.append(Connection(connection: newConnection, id: currentID)) bestConnectionID = currentID TCPConnection.nextID += 1 newConnection.stateUpdateHandler = { (newState) in switch (newState) { case .ready: self.chooseBestConnection() self.connectionStateChangedNotifier(connectionID: self.bestConnectionID, newStatusFlag: true, connectivityStatus: "ready") self.firstConnection = false case .waiting(let error): self.connectionStateChangedNotifier(connectionID: self.bestConnectionID, newStatusFlag: false, connectivityStatus: "waiting") self.debug("Waiting error: \(error)") if error == .posix(POSIXErrorCode.ECONNREFUSED) { self.retryTimer?.invalidate() self.retryTimer = Timer.scheduledTimer(withTimeInterval: 6.0, repeats: false) { timer in newConnection.restart() } self.debug("Trying to connect again after being refused") } else if error == .dns(-65554) { self.debug("Could not resolve hostname") if let alternative = self.alternativeEndpoint { self.connection = self.create(alternative) } } case .failed(let error): self.connectionStateChangedNotifier(connectionID: self.bestConnectionID, newStatusFlag: false, connectivityStatus: "failed") self.debug("Failed: \(error)") newConnection.cancel() self.start() case .cancelled: self.connectionStateChangedNotifier(connectionID: self.bestConnectionID, newStatusFlag: false, connectivityStatus: "cancelled") case .preparing: self.connectionStateChangedNotifier(connectionID: self.bestConnectionID, newStatusFlag: false, connectivityStatus: "preparing") default: break } } self.receive(on: newConnection) debug("Starting Network Dispatch Queue") newConnection.start(queue: self.myQueue) return newConnection } func chooseBestConnection() { if let bestCon = connections.filter({$0.connection.state == .ready && $0.id == bestConnectionID}).first { connection = bestCon.connection activeConnectionID = bestCon.id lookForPathUpdate(on: connection) lookForBetterPath(on: connection) lookForConnectivity(on: connection) } for (index, con) in connections.enumerated().reversed() { if con.id != activeConnectionID && con.id != bestConnectionID { close(con: con.connection) connections.remove(at: index) } } } func lookForConnectivity(on con:NWConnection) { con.viabilityUpdateHandler = { (isViable) in self.delegate?.viabilityChanged?(isViable: isViable) if (!isViable) { self.debug("Connectivity down!!!") self.delegate?.networkStatusChanged(online: false, connectivityStatus: "connectivity down") self.online = false } else { debugPrint("NETWORK - Connectivity up!!!") if con.state == .ready { self.delegate?.networkStatusChanged(online: true, connectivityStatus: "connectivity up") self.online = true } } } } func lookForBetterPath(on con:NWConnection) { con.betterPathUpdateHandler = { (betterPathAvailable) in if (betterPathAvailable) { self.debug("Better path available") self.start() } } } func lookForPathUpdate(on con:NWConnection) { con.pathUpdateHandler = { path in if path.status == .satisfied { self.debug("Path is satisfied") } else { self.debug("Path is not satisfied") } if path.usesInterfaceType(.wifi) { self.debuggerDelegate?.changedToPath("Wifi") } else if path.usesInterfaceType(.cellular) { self.debuggerDelegate?.changedToPath("Cellular") } } } func isReady() -> Bool { if(connection.state == .ready) { return true } return false } func isOnline() -> Bool { return online } func isClosed() -> Bool { if(connection.state == .cancelled) { return true } return false } func sendMsg(_ message: String, completion: @escaping (NetworkError?) -> Void = {_ in }) { let msg = message + "\r\n" let data: Data? = msg.data(using: .utf8) debug("Sending: \(msg)") connection.send(content: data, completion: .contentProcessed { (sendError) in if let sendError = sendError { self.debug("\(sendError)") self.debug("Failed to send: \(message)") completion(.failedToSend) return } completion(nil) }) } func sendPing(_ message: String) { let msg = message + "\r\n" let data: Data? = msg.data(using: .utf8) debug("Sending: \(msg)") connection.send(content: data, completion: .idempotent) } func receive(on con: NWConnection) { con.receive(minimumIncompleteLength: 1, maximumLength: 8192) { (content, context, isComplete, error) in if let content = content { let delimiter = "\r\n" let response = String(decoding: content, as: UTF8.self).components(separatedBy: delimiter) if response[0] == "PONG" { self.debug("PONG received!") } DispatchQueue.main.async { self.tellViewController(message: String(decoding: content, as: UTF8.self)) } self.debug("RECEIVED: \(String(decoding: content, as: UTF8.self))") } if isComplete { con.restart() self.debug("Received EOF") } else if let error = error { self.debug("Error receiving data - \(error)") } else if con.state == .ready && isComplete == false { self.receive(on: con) } } } func close(con: NWConnection) { con.stateUpdateHandler = nil con.viabilityUpdateHandler = nil con.betterPathUpdateHandler = nil con.pathUpdateHandler = nil if con.state != .cancelled { con.cancel() } } func close() { close(con: connection) } private func connectionStateChangedNotifier(connectionID: Int, newStatusFlag: Bool, connectivityStatus: String) { if connectionID == activeConnectionID { notifyDelegateOnChange(newStatusFlag: newStatusFlag, connectivityStatus: connectivityStatus) } } private func notifyDelegateOnChange(newStatusFlag: Bool, connectivityStatus: String) { debug("newStatusFlag: \(newStatusFlag), connectionState: \(connectivityStatus)") self.delegate?.networkStatusChanged(online: newStatusFlag, connectivityStatus: connectivityStatus) if newStatusFlag != self.online { self.online = newStatusFlag } } private func tellViewController(message: String) { DispatchQueue.main.async { self.delegate?.passData(data: message) } } private func debug(_ msg:String) { debuggerDelegate?.log(msg) } deinit { debugPrint("NETWORK - connection deinit") if connection.state != .cancelled { connection.cancel() } } }
Posted
by tar rocha.
Last updated
.
Post not yet marked as solved
2 Replies
934 Views
I'm using pushkit voip notifications to alert the users of my app when someone rings an intercom.The flow would go like this, someone would ring the intercom and it would send a voip notification to the users telling them "You have guests at your door", which after clicking the notification banner would send them to the intercom page consisting of a live camera feed with 1 button to take the SIP VoIp call and 1 button to open the door.But now on IOS13 it will crash my app because I'm not using callkit.The reason I'm not using callkit is because I want the user to be able to view the live video from the camera, before taking the call. More often than not depending on who they see in the camera the users won't even take the call, they will just press the button to open the door and close the app.How do I go about adapting this to IOS13 now, I need the notifications to have a similar priority to voip since they are essentially communicating a voip call anyway as you can see explained above.
Posted
by tar rocha.
Last updated
.
Post marked as solved
4 Replies
2.0k Views
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 followingclass 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) } } }
Posted
by tar rocha.
Last updated
.