Search code examples
iosswiftudpcocoaasyncsocket

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.


Solution

  • 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