Setting GCDAsyncUdpSocket listen port

1,605 views
Skip to first unread message

Tobin Fisher

unread,
Nov 4, 2013, 1:01:07 AM11/4/13
to cocoaasy...@googlegroups.com
I am using this library to set up a bi-directional UDP data stream from an iOS device to to a WiFly wifi module. Sending data is working great, but receiving data is problematic, due to how the remote ports are set on the wifi module. One can set up the module to dynamically assign ports based on the response from the host, which enables the app to receive data, but the way the port is saved in the module, the app cannot continue to receive data if it is restarted without power cycling the wifi module, as the app chooses a new port the next time a connection is established (based on using wireshark to sniff the stream), but the wifly module keeps sending to the original port. One can also set the remote port statically on the wifi module, which seems like the better way to go for my application, but I don't see how to set the port that GCDAsyncUdp listens to for receiving data. Tips on how to do this?

Many thanks for any help,

Tobin

Arno Gramatke

unread,
Nov 4, 2013, 4:35:56 AM11/4/13
to cocoaasy...@googlegroups.com
You can use the following method to bind to a port. You should do this before you send data for the first time.

- (BOOL)bindToPort:(uint16_t)port error:(NSError **)errPtr

Hope that helps.

--
You received this message because you are subscribed to the Google Groups "CocoaAsyncSocket" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cocoaasyncsock...@googlegroups.com.
To post to this group, send email to cocoaasy...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/cocoaasyncsocket/aa30d2e9-c4c5-4e4e-8c56-eee107f80060%40googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.


Tobin Fisher

unread,
Nov 4, 2013, 12:56:03 PM11/4/13
to cocoaasy...@googlegroups.com
Thanks for the quick response Arno.  

I currently use that method when setting up the socket, but it only appears to set the send port, not the listen port. I currently set the send port to 2000, but the app appears to listen on one of the dynamically assigned ports between 49152 and 65535, which I currently don't see how to set statically.



You received this message because you are subscribed to a topic in the Google Groups "CocoaAsyncSocket" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/cocoaasyncsocket/hV0uhxRsL4I/unsubscribe.
To unsubscribe from this group and all its topics, send an email to cocoaasyncsock...@googlegroups.com.

To post to this group, send email to cocoaasy...@googlegroups.com.

Arno Gramatke

unread,
Nov 4, 2013, 5:58:07 PM11/4/13
to cocoaasy...@googlegroups.com
Are you using the same object for sending that you use when you call bindToPort?

Maybe you can post some code - might be easier to help.

Tobin Fisher

unread,
Nov 4, 2013, 7:29:27 PM11/4/13
to cocoaasy...@googlegroups.com
Thanks again for the response.

Yes, I believe I am using the same object, _gcdUdpSock, for both sending and receiving. 

Here's the relevant code: 

- (void)connectViaUpdClientSocket
{
    NSLog(@"connectViaUpdClientSocket");
    [NSThread detachNewThreadSelector:@selector(startUdpClientConnection)
                             toTarget:self
                           withObject:nil];
}


- (void)startUdpClientConnection
{

    

    _gcdUdpSock = [[GCDAsyncUdpSocket alloc] initWithDelegate:self delegateQueue:dispatch_get_main_queue()];
    [_gcdUdpSock setIPv4Enabled:YES];
    [_gcdUdpSock setPreferIPv4];
    [_gcdUdpSock setIPv6Enabled:NO];

    

    NSError* err = nil;

    

    _recvBuf  = [[NSMutableData alloc] initWithCapacity:2048];

    

    NSString* userDefinedHost = kRepeaterIPAddress;
    NSUInteger userDefinedPort = kRepeaterPort;

    

    NSLog(@"Udp Client connecting to: %@:%d",userDefinedHost,userDefinedPort);

    

    BOOL success = [_gcdUdpSock connectToHost:userDefinedHost onPort:userDefinedPort error:&err ];
    if (!success || err != nil) {
        NSLog(@"UdpClient connect failed: %@",err);
    }
}


#pragma mark - GCDAsyncSocketDelegate
- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(uint16_t)port
{
    NSLog(@"didConnectToHost: %@:%d",host,port);
    //this indicates the true start of a connection to the mav
    _pipeConnected = YES;
    [self notifyDelegatesLinkConnectState];

    

    //This is kinda annoying...we need to repeatedly tell the socket to read
    [_gcdTcpSock readDataWithTimeout:(NSTimeInterval)30 tag:0];

}


- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    //NSLog(@"didReadData %d",[data length]);
    [_recvBuf appendData:data];
    [self notifyDelegatesOfDataReceived:data];

    

    //again kinda annoying...go read again
    [_gcdTcpSock readDataWithTimeout:(NSTimeInterval)30 tag:0];

}

- (void)socket:(GCDAsyncSocket *)sock didWriteDataWithTag:(long)tag
{
    id pendingData =(__bridge id)(void*)tag;
    //NSLog(@"didWriteDataWithTag: len %d",[pendingData length]);

    

    if (![_pendingWrites containsObject:pendingData]) {
        NSLog(@"Write finished for an object more than once??");
    }
    else {
        [_pendingWrites removeObject:pendingData];
        //TODO notify sender
    }
}

- (NSInteger)writeData:(NSData*)data
{
    NSInteger nWritten = 0;

    

    if (self.linkConnected) {
        NSInteger len = [data length];

        

        NSData* frozenData = [data copy];
        [_pendingWrites addObject:frozenData];

        if (nil != _gcdUdpSock) {
            //NSLog(@"writeData to: %@",_remoteAddress);

            

            //use the data's pointer in the _pendingWrites list as the tag GCDSock hands back to us
            [_gcdUdpSock sendData:frozenData toAddress:_remoteAddress withTimeout:(NSTimeInterval)5 tag:(long)frozenData];
            nWritten = len; //TODO la la la
        }
        else if (nil != _gcdTcpSock) {
            [_gcdTcpSock writeData:frozenData withTimeout:(NSTimeInterval)5 tag:(long)frozenData] ;
             nWritten = len; //TODO la la la
        }
    }
    else {
        NSLog(@"writeData inactive when link disconnected ");
    }

    

    return nWritten; //TODO nWritten is now effectively meaningless...maybe change interface
}


And here's how these methods are called externally:

- (void)sendMavlinkMessage:(mavlink_message_t*)pMsg {

    

    if (self.sharedData.connected) {
        @synchronized(_outputBuf) {
            [_outputBuf setLength:SEND_BUFFER_SIZE];
            [_outputBuf resetBytesInRange:NSMakeRange(0, SEND_BUFFER_SIZE)];//clear any existing output data
            uint8_t* buf = [_outputBuf mutableBytes];
            uint16_t len = mavlink_msg_to_send_buffer(buf, pMsg);
            if (len > 0) {
                [_outputBuf setLength:len];
                [self writeOutputData];
            }
        }
    }
}


- (void)writeOutputData
{
    [[LinkManager sharedInstance] writeData:_outputBuf];
}


And the full class in the event that it's useful:

LinkManager.m

Arno Gramatke

unread,
Nov 5, 2013, 9:02:39 AM11/5/13
to cocoaasy...@googlegroups.com
Tobin,

from what I can see, you are not binding the socket to a port (as I suggested in my previous mail), but you connect the socket to the remote host. Don’t mix up connecting (=remote) and binding (=local). connectToHost:onPort:error: just connects to the given remote port on the given remote host, but uses a random port on the local machine. If you need to use a specific local port you have to bind the socket before(!) connecting or sending data.

In your startUdpClientConnection method insert the following line before the call to connectToHost:onPort:error: and add error checking as appropriate:

BOOL whatever = [_gcdUdpSock bindToPort:yourLocalPort error:&err];

That should setup your socket to listen on yourLocalPort.

Apart from that I am a little confused by your code. :) Here are the things that got me confused:

1) Where do you start receiving? I don’t see a call to beginReceiving? The TCP and UDP variants of GCDAsyncSocket are somewhat different due to the underlying protocol. It looks like you started with a TCP setup and switched to UDP later on but didn’t adopt to the different interface.

2) You are mixing TCP and UDP stuff and I don’t see the delegate methods for the UDP part - only TCP delegates are visible in the code you posted. Even when you have done the above you won’t see any incoming data without the appropriate delegate methods. At least implement the following

- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext;

3) What are kRepeaterIPAddress and kRepeaterPort? Are these your local address and port or the remote ones? If they are the local ones, why do you connect to them? There is no need to do this. Just use bindToPort. By the way: your are sending data to _remoteAddress. Using this on a connected

4) Sorry for the harsh words, but the TCP parts (especially the comments) don’t look like you really understand what you are doing. It looks like you are just trying to receive arbitrary data without any idea of the underlying protocol. There is a reason why you have to tell GCDAsyncSocket to read data again and again. That might be very annoying in the beginning, but in return you get a very flexible system for handling networking data. Check the general introduction in the GCDAsyncSocket wiki on Github.

5) Why do you work with threading when using GCDAsyncUdpSocket? This should not be necessary as the networking stuff is running asynchronously in its own GCD dispatch queue. If you are worried about processing of the received data, you can also choose a different delegate queue if you like (in your code use use the main queue - you can set this).

For me it looks like you really need to clean up your code. Do you need both TCP and UDP?

Please don’t get me wrong. I made plenty of mistakes myself when I first started using GCDAsyncSocket und GCDAsyncUdpSocket over a year ago. But it’s all in the documentation. You just have to make yourself familiar with it. :)

Again, I hope that helps.

Arno

--
You received this message because you are subscribed to the Google Groups "CocoaAsyncSocket" group.
To unsubscribe from this group and stop receiving emails from it, send an email to cocoaasyncsock...@googlegroups.com.
To post to this group, send email to cocoaasy...@googlegroups.com.

For more options, visit https://groups.google.com/groups/opt_out.

<LinkManager.m>


Tobin Fisher

unread,
Nov 12, 2013, 10:36:03 PM11/12/13
to cocoaasy...@googlegroups.com
Thanks for the thorough and thoughtful response Amo and my apologies for my delayed response.

I've got it working well now, thanks in part to your feedback. See detailed responses to your comments below.

On Nov 5, 2013, at 6:02 AM, Arno Gramatke <ar...@gramatke.biz> wrote:

Tobin,

from what I can see, you are not binding the socket to a port (as I suggested in my previous mail), but you connect the socket to the remote host. Don’t mix up connecting (=remote) and binding (=local). connectToHost:onPort:error: just connects to the given remote port on the given remote host, but uses a random port on the local machine. If you need to use a specific local port you have to bind the socket before(!) connecting or sending data.

In your startUdpClientConnection method insert the following line before the call to connectToHost:onPort:error: and add error checking as appropriate:

BOOL whatever = [_gcdUdpSock bindToPort:yourLocalPort error:&err];

That should setup your socket to listen on yourLocalPort.

You are absolutely right. Thanks for explaining that!


Apart from that I am a little confused by your code. :) Here are the things that got me confused:

1) Where do you start receiving? I don’t see a call to beginReceiving? The TCP and UDP variants of GCDAsyncSocket are somewhat different due to the underlying protocol. It looks like you started with a TCP setup and switched to UDP later on but didn’t adopt to the different interface.

2) You are mixing TCP and UDP stuff and I don’t see the delegate methods for the UDP part - only TCP delegates are visible in the code you posted. Even when you have done the above you won’t see any incoming data without the appropriate delegate methods. At least implement the following

- (void)udpSocket:(GCDAsyncUdpSocket *)sock didReceiveData:(NSData *)data fromAddress:(NSData *)address withFilterContext:(id)filterContext;

Whoops. Looks like I sent the wrong delegate methods. Here's the correct code:

#pragma mark - GCDAsyncUdpSocketDelegate

- (void)udpSocketDidClose:(GCDAsyncUdpSocket *)sock withError:(NSError *)error
{
    NSLog(@"udpSocketDidClose:withError: %@",error);
    _remoteAddress = nil;
    _pipeConnected = NO;

    

}

- (void)udpSocket:(GCDAsyncUdpSocket *)sock didNotConnect:(NSError *)error {

    

    NSLog(@"didNotConnect: %@",error); //TODO inform the user / give them options
}

- (void)udpSocket:(GCDAsyncUdpSocket *)sock didConnectToAddress:(NSData *)address
{
    NSLog(@"udpSocket:didConnectToAddress: %@",address);
    _remoteAddress = address;
    _pipeConnected = YES;

    

    NSError* err = nil;
    BOOL success = [_gcdUdpSock beginReceiving:&err];
    if (!success || (nil != err)) {
        NSLog(@"beginReceiving err: %@",err);
    }
    else {
        [self notifyDelegatesLinkConnectState];
    }
}



3) What are kRepeaterIPAddress and kRepeaterPort? Are these your local address and port or the remote ones? If they are the local ones, why do you connect to them? There is no need to do this. Just use bindToPort.

They are the remote IP address and port.

By the way: your are sending data to _remoteAddress. Using this on a connected

4) Sorry for the harsh words, but the TCP parts (especially the comments) don’t look like you really understand what you are doing. It looks like you are just trying to receive arbitrary data without any idea of the underlying protocol. There is a reason why you have to tell GCDAsyncSocket to read data again and again. That might be very annoying in the beginning, but in return you get a very flexible system for handling networking data. Check the general introduction in the GCDAsyncSocket wiki on Github.

No worries on the harsh words. I definitely don't know what I'm doing just yet, but I'm hoping to learn and get it right. I'd love any thoughts you have on the updated code.


5) Why do you work with threading when using GCDAsyncUdpSocket? This should not be necessary as the networking stuff is running asynchronously in its own GCD dispatch queue. If you are worried about processing of the received data, you can also choose a different delegate queue if you like (in your code use use the main queue - you can set this).

You are absolutely correct. I changed this and it works great and is much cleaner.


For me it looks like you really need to clean up your code. Do you need both TCP and UDP?

Nope. I got TCP working first, so it was useful for debugging when UDP wasn't working. I plan to strip it out now that UDP is working.


Please don’t get me wrong. I made plenty of mistakes myself when I first started using GCDAsyncSocket und GCDAsyncUdpSocket over a year ago. But it’s all in the documentation. You just have to make yourself familiar with it. :)

Again, I hope that helps.

Arno

Many thanks again Arno!


Arno Gramatke

unread,
Nov 17, 2013, 12:52:04 PM11/17/13
to cocoaasy...@googlegroups.com
Hey Tobin,

thanks for lettings us know that it does work now. Your updated code looks good for me.

Cheers
Arno

Reply all
Reply to author
Forward
0 new messages