NWConnection timeout - ios

this is my first post to the community, so please be patient ;-)
I am using Apples Network framework to build a TCP connection to a server. It locks like this:
let testConnection = NWConnection(host: NWEndpoint.Host(server!.serverAddress), port: NWEndpoint.Port(server!.serverPort)!, using: .tcp)
testConnection.stateUpdateHandler = ({ state in
print("TCP state change to: \(state)")
switch state {
case .setup:
break
case .waiting(let error):
print("Waiting Error \(error)")
testConnection.cancel()
break
case .preparing:
break
case .ready:
beginCommunication()
break
case .failed(let error):
print("\(error)")
break
case .cancelled:
break
default:
break
}
})
The serverAddress and serverPort is entered by the user. I want to test the connection. Now my problem is, that if the user is entering an invalid address / port combination (the service is not offered by the server). I stuck in the preparing state for quite a long time. After that I get to the waiting stage (with error message POSIXErrorCode: Operation timed out).
Is there any way to set the timeout for this first connection process ?
Thanks for your ideas

You can set connection timed out by NWProtocolTCP.Options
lazy var tcpOptions: NWProtocolTCP.Options = {
let options = NWProtocolTCP.Options()
options.connectionTimeout = 5 // connection timed out
return options
}()
lazy var parames: NWParameters = {
let parames = NWParameters(tls: nil, tcp: self.tcpOptions)
if let isOption = parames.defaultProtocolStack.internetProtocol as? NWProtocolIP.Options {
isOption.version = .v4
}
parames.preferNoProxies = true
return parames
}()
lazy var connection: NWConnection = {
let connection = NWConnection(host: "x.x.x.x", port: xxxx, using: self.parames)
return connection
}()
When trigger the timed out event, the .waiting state will call back.
2021-08-30 23:05:17.xxxxxx+xxxx Demo[xxxx:xxxxxx] [connection] nw_socket_handle_socket_event [C1:1] Socket SO_ERROR [60: Operation timed out]
The connection is waiting for a network path change with: POSIXErrorCode: Operation timed out
reference from Developer Fourms

Related

How can I detect a server ping using URLSessionWebSocketTask?

Using URLSessionWebSocketTask, a server ping is automatically responded to by a pong from the client.
Still, can the client observe that a ping/pong occurred?
(a related but different question)
Usually, right after setting a connection, you'd need to run ping in a loop in the app:
Timer.TimerPublisher(interval: 10, runLoop: .main).sink(receiveValue: { _ in
task.sendPing(pongReceiveHandler: { error in
guard let error = error else {
log("Received pong")
return
}
// Handle error
})
}).store(in: &subscriptions)
UPD:
If you're interested in observing pong, then in subscription, check if you received 0x9 as a data:
task.receive { result in
switch result {
case .success(let message):
switch message {
case .data(let data):
//
...
}
...
}
}
It will be:
close connection: 0x8
ping: 0x9
pong: 0xA
So you can create an enum:
enum Code: UInt8 {
case closeConnection = 0x8
case ping = 0x9
case pong = 0xA
}
And initialise it with data you receive: Code(rawValue: YOURDATA)
Or you can send "PING" as a text from your cloud server instead, that will simplify things for you

How to create NWConnection

Premise
I'm using Apple's Network framework to create a peer-to-peer app. What I'm currently trying to do is:
Device A establishes a connection with Device B and sends data.
Device B saves the NWConnection information of Device A.
Device B establishes a connection with Device A again using the saved MWConnection information and sends data.
I modelled off Apple's demo, which is essentially this source code here. Unlike Apple's demo, which establishes a single two-way connection and maintains that connection at all times, my app connects to multiple devices that can drop in and out of the connections at any time. This is why I want to be able to a) distinguish a specific device and b) initiate a new connection.
Problem
The problem I'm having is being able to reconstruct the NWConnection object using the information provided when a connection has been established. There are two ways of instantiating this object.
init(host: NWEndpoint.Host, port: NWEndpoint.Port, using: NWParameters)
init(to: NWEndpoint, using: NWParameters)
My attempts have been trying to gather the endpoint information like the host and the port while the connection with the desired device has been established and instantiate NWEndpoint to be used in NWConnection. But, I haven't been able to re-establish the connection thus far.
Following is the portion of the object that is used to initiate a connection. My full code is modified, but Apple's counterpart can be seen here.
class PeerConnection {
weak var delegate: PeerConnectionDelegate?
weak var statusDelegate: StatusDelegate?
var connection: NWConnection?
let initiatedConnection: Bool
init(endpoint: NWEndpoint, interface: NWInterface?, passcode: String, delegate: PeerConnectionDelegate) {
self.delegate = delegate
self.initiatedConnection = true
let connection = NWConnection(to: endpoint, using: NWParameters(passcode: passcode))
self.connection = connection
startConnection()
}
init(connection: NWConnection, delegate: PeerConnectionDelegate) {
self.delegate = delegate
self.connection = connection
self.initiatedConnection = false
startConnection()
}
// Handle starting the peer-to-peer connection for both inbound and outbound connections.
func startConnection() {
guard let connection = connection else {
return
}
connection.stateUpdateHandler = { newState in
switch newState {
case .ready:
print("\(connection) established")
// When the connection is ready, start receiving messages.
self.receiveNextMessage()
// Notify your delegate that the connection is ready.
if let delegate = self.statusDelegate {
delegate.showConnectionStatus(.connectionSuccess("Connection Success"))
}
case .failed(let error):
print("\(connection) failed with \(error)")
// Cancel the connection upon a failure.
connection.cancel()
// Notify your delegate that the connection failed.
if let delegate = self.statusDelegate {
delegate.showConnectionStatus(.connectionFail("Connection Fail"))
}
default:
break
}
}
// Start the connection establishment.
connection.start(queue: .main)
}
}
This is how I receive data when another device sends it.
func receiveNextMessage() {
guard let connection = connection else {
return
}
/// Has to call itself again within the closure because the maximum message is once.
connection.receiveMessage { (content, context, isComplete, error) in
// Extract your message type from the received context.
if let message = context?.protocolMetadata(definition: GameProtocol.definition) as? NWProtocolFramer.Message {
self.delegate?.receivedMessage(content: content, message: message, connection: connection)
}
if error == nil {
// Continue to receive more messages until you receive and error.
self.receiveNextMessage()
}
}
}
Finally, following is how I attempt to reconstruct MWConnection:
func receivedMessage(content: Data?, message: NWProtocolFramer.Message, connection: NWConnection) {
switch(connection.endpoint) {
case .hostPort(let host, let port):
/// first attempt
let endpoint = NWEndpoint.hostPort(host: host, port: port)
let newConnection = PeerConnection(endpoint: endpoint, interface: nil, passcode: passcode, delegate: self)
/// second attempt
let connection = NWConnection(host: host, port: port, using: NWParameters(passcode: passcode))
let newConnection = PeerConnection(connection: connection, delegate: self)
default:
break
}
}
NWConnection is instantiated inside initializer of PeerConnection.

NWNetworking / NWListener how to correctly receive UDP broadcast messages?

I have the following code running fine when receiving direct messages via UDP:
import UIKit
import Network
class ViewController: UIViewController {
var udpListener:NWListener?
var backgroundQueueUdpListener = DispatchQueue(label: "udp-lis.bg.queue", attributes: [])
var backgroundQueueUdpConnection = DispatchQueue(label: "udp-con.bg.queue", attributes: [])
var connections = [NWConnection]()
override func viewDidLoad() {
super.viewDidLoad()
myOnButton(self)
}
#IBAction func myOnButton(_ sender: Any) {
guard self.udpListener == nil else {
print(" ๐Ÿงจ Already listening. Not starting again")
return
}
do {
self.udpListener = try NWListener(using: .udp, on: 2000)
self.udpListener?.stateUpdateHandler = { (listenerState) in
switch listenerState {
case .setup:
print("Listener: Setup")
case .waiting(let error):
print("Listener: Waiting \(error)")
case .ready:
print("Listener: Ready and listens on port: \(self.udpListener?.port?.debugDescription ?? "-")")
case .failed(let error):
print("Listener: Failed \(error)")
case .cancelled:
print("Listener: Cancelled by myOffButton")
for connection in self.connections {
connection.cancel()
}
self.udpListener = nil
default:
break;
}
}
self.udpListener?.start(queue: backgroundQueueUdpListener)
self.udpListener?.newConnectionHandler = { (incomingUdpConnection) in
print ("๐Ÿ’ New connection \(incomingUdpConnection.debugDescription)")
incomingUdpConnection.stateUpdateHandler = { (udpConnectionState) in
switch udpConnectionState {
case .setup:
print("Connection: setup")
case .waiting(let error):
print("Connection: waiting: \(error)")
case .ready:
print("Connection: ready")
self.connections.append(incomingUdpConnection)
self.processData(incomingUdpConnection)
case .failed(let error):
print("Connection: failed: \(error)")
self.connections.removeAll(where: {incomingUdpConnection === $0})
case .cancelled:
print("Connection: cancelled")
self.connections.removeAll(where: {incomingUdpConnection === $0})
default:
break
}
}
incomingUdpConnection.start(queue: self.backgroundQueueUdpConnection)
}
} catch {
print("๐Ÿงจ")
}
}
#IBAction func myOffButton(_ sender: Any) {
udpListener?.cancel()
}
func processData(_ incomingUdpConnection :NWConnection) {
incomingUdpConnection.receiveMessage(completion: {(data, context, isComplete, error) in
if let data = data, !data.isEmpty {
if let string = String(data: data, encoding: .ascii) {
print ("DATA = \(string)")
}
}
if error == nil {
self.processData(incomingUdpConnection)
}
})
}
}
Sending endless UDP packages directly to the device like this:
echo -n "Testdata" | socat - udp-datagram:192.168.2.126:2000,broadcast,sourceport=2000
works as expected, and all messages arrive:
Listener: Waiting POSIXErrorCode: Network is down
Listener: Ready and listens on port: 2000
๐Ÿ’ New connection [C1 192.168.2.134:2000 udp, local: 192.168.2.126:2000, indefinite, server, path satisfied (Path is satisfied), interface: en0, scoped, ipv4, ipv6, dns]
Connection: ready
DATA = Testdata
DATA = Testdata
DATA = Testdata
DATA = Testdata
...
But trying the same with broadcast messages
echo -n "Testat" | socat - udp-datagram:255.255.255.255:2000,broadcast,sourceport=2000
results in only receiving the first message, and a new connection with each received UDP package.
Listener: Waiting POSIXErrorCode: Network is down
Listener: Ready and listens on port: 2000
๐Ÿ’ New connection [C1 192.168.2.134:2000 udp, local: 0.0.0.0:2000, indefinite, server, path satisfied (Path is satisfied), interface: en0, ipv4, ipv6, dns]
Connection: ready
DATA = Testdata
2020-06-28 14:22:03.668116+0200 networking[25440:13489733] [] nw_channel_reclassify_input reclassify failed, could not find client for slot 77926755-A281-4AFD-9649-92FBD1A21FA6
๐Ÿ’ New connection [C2 192.168.2.134:2000 udp, local: 0.0.0.0:2000, indefinite, server, path satisfied (Path is satisfied), interface: en0, ipv4, ipv6, dns]
2020-06-28 14:22:04.384443+0200 networking[25440:13489733] [] nw_channel_reclassify_input reclassify failed, could not find client for slot 7EAC98FA-F665-43C1-9B15-B68B74A56BBC
๐Ÿ’ New connection [C3 192.168.2.134:2000 udp, local: 0.0.0.0:2000, indefinite, server, path satisfied (Path is satisfied), interface: en0, ipv4, ipv6, dns]
2020-06-28 14:22:05.096808+0200 networking[25440:13489733] [] nw_channel_reclassify_input reclassify failed, could not find client for slot AA3EE3BA-D891-4D07-87AF-0CB0A9382CDF
๐Ÿ’ New connection [C4 192.168.2.134:2000 udp, local: 0.0.0.0:2000, indefinite, server, path satisfied (Path is satisfied), interface: en0, ipv4, ipv6, dns]
2020-06-28 14:22:05.714651+0200 networking[25440:13489733] [] nw_channel_reclassify_input reclassify failed, could not find client for slot ED108E8B-E9BC-43C3-8AA7-B4BD0515EB54
Workaround would be to .cancel the connection after each received package, but I doubt this is the way how this was intended to be used to receive UDP broadcast messages. So what is the correct approach to receive UDP broadcast packages in an endless loop?
Ok, I got sort of an answer.
I had the same basic issue in Objective-C. I took the step of cancelling the connection in the handler, which sort of works, but you end up with data loss - about 30% in my experience - as the stream fails to pick up broadcast data.
However, I did get feedback from dts, which is basically this - Networking Library is not designed for receiving broadcast UDP.
I've reverted to BSD sockets.

How to connect Starscream iOS client to Java webserver

I'm running Spring Boot v2.2 with a correct WebSocketHandler(). I'm confident the server is correct because when I go to http://websocket.org/echo.html and attempt to connect to our server, we can verify connection on both the server and the browser client.
However, in iOS (I'm testing on 2 simulators - iOS 12 and iOS 13.3), I'm not able to connect. I'm now attempting to utilize Starscream. (attempting this with Socket.io led to unsolvable issues and attempting this with SocketRocket led to issues simply getting it to build on iOS 13.3.)
The issue I'm facing now is that Starscream just fails silently when attempting to connect to the address of our java server (ws://127.0.0.1:8080/socket). When I say fail silently, I mean that nothing happens on the client or server indicating that there was an error but also by debugging I can see that isConnected = false on our iOS socket.
To attempt to fix this issue I've tried:
adding App Transport Security Settings -> Allow Arbitrary Loads = YES in Info.plist.
adding NSExceptionDomains -> NSExceptionAllowsInsecureHTTPLoads = YES in Info.plist.
utilizing both localhost and 127.0.0.1, both with /socket or / and HTTP instead of ws/wss.
I was even able to effectively query google.com with a GET request using native Swift.
import Foundation
import Starscream
class WinkNetworkClient : WebSocketDelegate {
private var isConnected : Bool = false
init() {
let socket: WebSocket =
WebSocket(request:
URLRequest(url: URL(string: "ws://127.0.0.1:8080/socket")!), certPinner: FoundationSecurity(allowSelfSigned: true))
socket.delegate = self
socket.connect()
// socket.write(string: "Hi Server!")
print("Client done")
}
func didReceive(event: WebSocketEvent, client: WebSocket) {
switch event {
case .connected(let headers):
isConnected = true
print("websocket is connected: \(headers)")
case .disconnected(let reason, let code):
isConnected = false
print("websocket is disconnected: \(reason) with code: \(code)")
case .text(let string):
print("Received text: \(string)")
case .binary(let data):
print("Received data: \(data.count)")
case .ping(_):
break
case .pong(_):
break
case .viabilityChanged(_):
break
case .reconnectSuggested(_):
break
case .cancelled:
isConnected = false
case .error(let error):
isConnected = false
print("error connecting to websocket: \(String(describing: error))")
}
}
}
I'm very lost as to what the issue might be. What am I doing wrong?

Request not firing when returning to app from background

I'm returning to my app which is in the background from a successful payment from another app. When returning, a patch request should be fired to change status from available to sold. The problem is that the request is never fired.
I use alamofire session manager to make the request. And the function making the request is used all over the project and it works. It is also not a server error or a threading error as it runs on the main thread and the server works through curl / swagger.
It seems to me that the app does not have internet connection or something of the sort when returning to the app. Loggs can be found down below. The request has been succesfully fired a couple of times, which makes be believe in the internet connection theory.
Here are some selected parts of the code.
lazy var manager: SessionManager = {
let sessionManager = Alamofire.SessionManager.default
sessionManager.session.configuration.timeoutIntervalForRequest = 40
sessionManager.adapter = OauthHandler.sharedInstance
sessionManager.retrier = OauthHandler.sharedInstance
return sessionManager
}()
func authenticateRequestWithRouter(_ router: BaseRouter, shouldShowErrorAlert: Bool = true, _ completion: requestResponse?) {
let request = router.asURLRequest()
manager.request(request).validate().responseJSON() { [weak self] response in
switch response.result {
case .success(let data):
completion?(true, data, nil)
case .failure(let error):
if shouldShowErrorAlert,
let errorMessage = self?.parseErrorMessage(fromData: response.data, request: request) {
self?.showErrorAlert(message: errorMessage)
}
completion?(false, response.data, error)
}
}
}
Logs that have been occuring:
Returning ENOTCONN because protocol has not yet been set up
nw_connection_copy_connected_local_endpoint [C7] Connection has no
local endpoint
HTTP load failed (error code: -1005 [1:57])
Domain=NSURLErrorDomain Code=-1005 "The network connection was lost."
Domain=NSPOSIXErrorDomain Code=53 "Software caused connection abort"
nw_protocol_boringssl_error(1584) [C1.1:2][0x102838b30] Lower protocol
stack error: 53

Resources