Swift: In NWConnection class when does receiveMessage(completion:) gets called - ios

I wanna have UDP connection with a wifi module as a server and an iOS as a client and send/receive data (obviously).First i wanted to use Sockets but then i realized Apple has introduced Network Framework; So i used NWConnection which is a class from Network Framework for my purpose and i was successful at sending the data to the device but unable to receive the response (which i am sure exists as i monitor devices I/O packets via serial port monitor).Here is a test version of the code where i use netcat as server to test the connection:
ViewController Class
import UIKit
import Network
class ViewController: UIViewController
{
var network: UDPNetwork!
override func viewDidLoad()
{
super.viewDidLoad()
}
#IBAction func button(_ sender: Any)
{
self.network = UDPNetwork(host: "192.168.200.4", port:"4210")!
self.network.connect()
}
}
UDPNetwork Class
import Foundation
import Network
class UDPNetwork {
var hostUDP: NWEndpoint.Host
var portUDP: NWEndpoint.Port
private var connection: NWConnection?
private var queue = DispatchQueue(label: "NetworkQuue", qos: .utility)
init?(host: String, port: String) {
guard !host.isEmpty, let portUDP = NWEndpoint.Port(port) else {
return nil
}
self.hostUDP = NWEndpoint.Host(host)
self.portUDP = portUDP
}
func connect()
{
connection = NWConnection(host: hostUDP, port: portUDP, using: .udp)
connection?.stateUpdateHandler =
{
(newState) in switch (newState)
{
case .ready:
//The connection is established and ready to send and recieve data.
print("ready")
self.sendPaket("Hello")
self.receive()
case .setup:
//The connection has been initialized but not started
print("setup")
case .cancelled:
//The connection has been cancelled
print("cancelled")
case .preparing:
//The connection in the process of being established
print("Preparing")
default:
//The connection has disconnected or encountered an error
print("waiting or failed")
}
}
connection?.start(queue: self.queue)
}
func sendPaket(_ packet:String)
{
let packetData = packet.data(using: .utf8)
self.connection?.send(content: packetData, completion: NWConnection.SendCompletion.contentProcessed(({ (error) in
if let err = error {
print("Sending error \(err)")
} else {
print("Sent successfully")
}
})))
}
func receive()
{
self.connection?.receiveMessage(completion:
{
(data, context, isComplete, error) in
print("Got it")
if let err = error {
print("Recieve error: \(err)")
}
if let rcvData = data,
let str = String(data:rcvData, encoding: .utf8) {
print("Received: \(str)")
}
self.receive()
})
}
}
Apple Documentation says:
receiveMessage(completion:)
Schedules a single receive completion handler for a complete message
completion :
A receive completion is invoked exactly once for a call to receive.
My Question is:
How can we call to receive ?
Assuming the receiveMessage(completion:) method is the Call to receive and also after receiving complete message calls the completion itself, What could be the problem if it doesn't get invoked?
Related code and more detail about my use case can be found here:
Swift: Receiving UDP packets from server on serial port monitor but not in ios app
With these assumptions :
We call to receive on the same connection that we send data to
UDP is used method for connection
Server is tested for swapping ports correctly, meaning it responds on the same IP,Port that data is sent to
My Setup:
13" MacBookPro Early 2015 with MacOS Catalina 10.15
Xcode Version 11.0 (11A420a)
Swift 5.1
target iOS 12+
And this is the result from Wire Shark:

Related

stopVPNTunnel is not disconnecting the VPN connection in iOS

I have implemented one successful vpn connection. But When i close and Open app while the VPN is connected, then i can't disconnect the VPN.
public func connectVPN() {
//For no known reason the process of saving/loading the VPN configurations fails.On the 2nd time it works
Log.d(message: "connectVPN")
NSLog("ConnectVPN")
self.vpnManager.loadFromPreferences(completionHandler: self.vpnLoadHandler)
NotificationCenter.default.addObserver(forName: NSNotification.Name.NEVPNStatusDidChange, object: nil , queue: nil) {
notification in
let nevpnconn = notification.object as! NEVPNConnection
let status = nevpnconn.status
self.vpnDelegate?.checkNES(status: status)
}
}
public func disconnectVPN() ->Void {
Log.d(message: "Disconnect VPN Called")
Log.d(message: "Log Disconnect VPN Called")
print("vpnManager:disconnectVPN \(self.vpnManager.connection) ") // object available
self.vpnManager.connection.stopVPNTunnel()
}
I could not find out why it is not disconnecting.
Call stopVPNTunnel() inside loadFromPreferences closure.
NEVPNManager.shared().loadFromPreferences { error in
assert(error == nil, "Failed to load preferences: \(error!.localizedDescription)")
NEVPNManager.shared().connection.stopVPNTunnel()
}

Socket io doesn't connect after calling disconnect function .i.e Connect -> Disconnect -> Connect won't work iOS swift

I am working on Chat engine using Socket io version 15.2.0,
When first time when I call socket.connect() it works properly, then if I call socket.disconnect() and then again try to call socket.connect() then socket fails to connect without any error or log. Then I have to kill the app and need to relaunch the app then socket connects without any issue.
following is my Shared class for Socket connect and disconnect
class SocketHelper {
static let shared = SocketHelper()
var socket: SocketIOClient!
let manager = SocketManager(socketURL: URL(string: ApiUrls.socketUrl)!, config: [.log(true), .compress, .connectParams(["token": Globals.shared.session.accessToken]), .secure(true), .forceNew(true)])
private init() {
socket = self.manager.defaultSocket
}
func getSocketWithLazyConnect() -> SocketIOClient {
if self.socket.status == .connected {
return self.socket
}
self.socket.connect()
return self.socket
}
func disconnect() {
self.socket.disconnect()
}
func isConnected() -> Bool {
return self.socket.status == .connected
}
}
Any help much appreciated...
Thank you guys but I figured out what I was doing wrong
I have one shared class named ChatEngine it handle all chat related socket events.
in that method while to disconnect from socket, I wrote a function switchOff
func switchOff() {
socket?.off(Constants.SocketEvent.connect)
SocketHelper.shared.disconnect()
}
as shown above I was calling off funtion for connect event and that's why after that when I called socket.connect, I didn't received any call back for that inside
self.socket?.on(clientEvent: .connect, callback: { (data, ack) in
})
I just removed off function call from my switchOff function and now it's working fine. Now my switchOff function is like this
func switchOff() {
SocketHelper.shared.disconnect()
}

How can I call asynchronously a method only when other async method finishes working in Swift?

I'm implementing socket.io library for swift.
There is a method related to connecting to the server that looks as follows:
func connectToServerWithNickname(nickname: String) {
socket.emit("connectUser", nickname)
}
The socket.emit calls:
private func _emit(data: [AnyObject], ack: Int? = nil) {
dispatch_async(emitQueue) {
guard self.status == .Connected else {
self.handleEvent("error", data: ["Tried emitting when not connected"], isInternalMessage: true)
return
}
let packet = SocketPacket.packetFromEmit(data, id: ack ?? -1, nsp: self.nsp, ack: false)
let str = packet.packetString
DefaultSocketLogger.Logger.log("Emitting: %#", type: self.logType, args: str)
self.engine?.send(str, withData: packet.binary)
}
}
as you can see it's all packed in dispatch_async. I would like to post an NSNotification as soon as this method is done and my app connects to the server, sth like:
NSNotificationCenter.defaultCenter().postNotificationName(setConnectionStatus, object: self)
So my question is - how should I modify my connectToServerWithNickname method so that it calls my nsnotificationcenter when everything goes correct?
After you stated you are using socket.IO
When you make your instance of the socket before you connect you will have
var socket: SocketIOClient = SocketIOClient(socketURL: NSURL(string: "http://192.168.1.XXX:3000")!)
then after this put
socket.on("connect") { (data, ack) -> Void in
// This will be fired once your socket connects.
}
EDIT:
For individual emits, you would keep a pointer to your socket and use
let json = ["things_i_need_to_send" : stuff]
socket.emitWithAck(Place_i_need_to_send_to, json)(timeoutAfter: 3) { data in
//This is called on completion
}
So using your above example.
socket.emitWithAck(connectUser, nickname)(timeoutAfter: 3) { data in
//This is called on completion
}

UDP Broadcast/Device Discovery on iOS?

I am working on trying develop a means of discovering Logitech Harmony Hub devices on my local network, from an iOS app. The concept is inspired by this NODE.JS project, which seems to send out a UDP broadcast to the 255.255.255.255 address, and then procures the Logitech's IP address (which is all I'm after). When testing the NODE.JS project on my home network from my Mac, it successfully finds the Logitech Harmony Hub.
I am using CocoaASyncSocket, and must admit, my understanding of how UDP broadcast/discovery works may be askew here. Here's what I'm doing;
import UIKit
import CocoaAsyncSocket
class ViewController: UIViewController, GCDAsyncUdpSocketDelegate {
var address = "255.255.255.255"
var port:UInt16 = 5224
var socket:GCDAsyncUdpSocket!
var socketReceive:GCDAsyncUdpSocket!
var error : NSError?
override func viewDidLoad() {
super.viewDidLoad()
let message = "_logitech-reverse-bonjour._tcp.local.\n61991".dataUsingEncoding(NSUTF8StringEncoding)
socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
socket.sendData(message, toHost: address, port: port, withTimeout: 1000, tag: 0)
do {
try socket.bindToPort(port)
} catch {
print(error)
}
do {
try socket.enableBroadcast(true)
} catch {
print(error)
}
do {
try socket.beginReceiving()
} catch {
print(error)
}
}
func udpSocket(sock: GCDAsyncUdpSocket!, didConnectToAddress address: NSData!) {
print("didConnectToAddress");
}
func udpSocket(sock: GCDAsyncUdpSocket!, didNotConnect error: NSError!) {
print("didNotConnect \(error)")
}
func udpSocket(sock: GCDAsyncUdpSocket!, didSendDataWithTag tag: Int) {
print("didSendDataWithTag")
}
func udpSocket(sock: GCDAsyncUdpSocket!, didNotSendDataWithTag tag: Int, dueToError error: NSError!) {
print("didNotSendDataWithTag")
}
func udpSocket(sock: GCDAsyncUdpSocket!, didReceiveData data: NSData!, fromAddress address: NSData!, withFilterContext filterContext: AnyObject!) {
var host: NSString?
var port1: UInt16 = 0
GCDAsyncUdpSocket.getHost(&host, port: &port1, fromAddress: address)
print("From \(host!)")
let gotdata: NSString = NSString(data: data!, encoding: NSUTF8StringEncoding)!
print(gotdata)
}
}
When I compile this, the only response I get is the message I just sent out;
didSendDataWithTag
From ::ffff:192.168.1.101
_logitech-reverse-bonjour._tcp.local.
61991
From 192.168.1.101
_logitech-reverse-bonjour._tcp.local.
61991
I fear that I have a conceptual understanding issue with the broadcast here, and am sincerely hoping that someone may be able to point me to a resource or help to understand why I'm not getting any response from the device in my code.
Thanks!
From the looks of the code it seems that you have only implemented half of the solution. The way it works is:
A broadcast message is sent to port 5224. This message includes the string logitech-reverse-bonjour._tcp.local. plus the port number that the Harmony should connect back to - in your case you have hardcoded 61991.
Presumably the Harmony receives this packet, recognises the message and then initiates a connection back to the device that sent the broadcast on the nominated port (61991 in this case).
Since your app is not listening on this port you don't get any response. This is implemented in the responseCollector.js file in the node.js project

Implementing A UDP "Listener" In Swift?

I've been trying to somewhat reverse engineer a project to discover Logitech Harmony Hub devices on my network, and posted this question to see if someone could help me understand UDP broadcast. The answer explained that I've implementing the send portion of the UDP broadcast, but I've not implemented anything to "listen" for responses. And that's where I'm struggling. Here's my send code;
import UIKit
import CocoaAsyncSocket
class ViewController: UIViewController, GCDAsyncUdpSocketDelegate {
var address = "255.255.255.255"
var port:UInt16 = 5224
var socket:GCDAsyncUdpSocket!
var socketReceive:GCDAsyncUdpSocket!
var error : NSError?
override func viewDidLoad() {
super.viewDidLoad()
let message = "_logitech-reverse-bonjour._tcp.local.\n61991".dataUsingEncoding(NSUTF8StringEncoding)
socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
socket.sendData(message, toHost: address, port: port, withTimeout: 1000, tag: 0)
do {
try socket.enableBroadcast(true)
} catch {
print(error)
}
}
func udpSocket(sock: GCDAsyncUdpSocket!, didConnectToAddress address: NSData!) {
print("didConnectToAddress");
}
func udpSocket(sock: GCDAsyncUdpSocket!, didNotConnect error: NSError!) {
print("didNotConnect \(error)")
}
func udpSocket(sock: GCDAsyncUdpSocket!, didSendDataWithTag tag: Int) {
print("didSendDataWithTag")
}
func udpSocket(sock: GCDAsyncUdpSocket!, didNotSendDataWithTag tag: Int, dueToError error: NSError!) {
print("didNotSendDataWithTag")
}
func udpSocket(sock: GCDAsyncUdpSocket!, didReceiveData data: NSData!, fromAddress address: NSData!, withFilterContext filterContext: AnyObject!) {
var host: NSString?
var port1: UInt16 = 0
GCDAsyncUdpSocket.getHost(&host, port: &port1, fromAddress: address)
print("From \(host!)")
let gotdata: NSString = NSString(data: data!, encoding: NSUTF8StringEncoding)!
print(gotdata)
}
}
I see I have the code to handle the response (in didReceiveData), but I'm unsure what I need to implement to get the listening going;
Do I need to "bind" to the listener port (in this case, 61991)?
Do I need to "join the multicast group"? And if so, at what address? I tried doing so at "255.255.255.255", which creates a setSocketOpt() error when I build.
I know I need to call beginReceiving(), and can I do all this on socket, or do I need to instantiate a separate socket for the listening?
Edit: Resolved
The below answer absolutely helped me to solve the problem. It seemed I wasn't getting a response because I had not fully implemented a means of handling the incoming response.
Per the code provided in the answer below, I added the following;
// Setup the other socket (used to handle the response from the Harmony hub)
otherSocket = GCDAsyncSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
do {
// Accept connections on port 61991
try otherSocket.acceptOnPort(61991)
} catch {
// Handle any errors here
print(error)
}
I also set this controller to be a GCDAsyncSocketDelegate, which seemed to do the trick. I was able to read the response in didReadData.
The following code changes enabled me to receive UDP packets that I sent from my Mac using netcat, but my Harmony hub didn't seem to send anything, so I am not sure if the data that is being sent is correct.
override func viewDidLoad() {
super.viewDidLoad()
let message = "_logitech-reverse-bonjour._tcp.local.\n61991".dataUsingEncoding(NSUTF8StringEncoding)
socket = GCDAsyncUdpSocket(delegate: self, delegateQueue: dispatch_get_main_queue())
do {
try self.socket.bindToPort(61991)
try self.socket.beginReceiving()
try socket.enableBroadcast(true)
socket.sendData(message, toHost: address, port: port, withTimeout: 1000, tag: 0)
} catch {
print(error)
}
}
From the command line you can test receiving using the command
echo -n "hello" | nc -4u -w1 x.x.x.x 61991

Resources