Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How do I get didReadData within GCDAsyncSocket execute within the current RunLoop?

I'm trying to get a simple example working with GCDAsyncSocket, and am discovering that I'm missing certain bits of understanding and hope you fine people can help explain this.

I've setup the GCDAsyncSocket stuff below:

dispatch_queue_t mainQueue = dispatch_get_main_queue();
asyncSocket = [[GCDAsyncSocket alloc] initWithDelegate:self delegateQueue:mainQueue];

NSString *host = @"192.168.169.132";
uint16_t port = 2112;

DDLogInfo(@"Connecting to \"%@\" on port %hu...", host, port);
self.viewController.label.text = @"Connecting...";

NSError *error = nil;
if (![asyncSocket connectToHost:host onPort:port withTimeout:5.0 error:&error])
{
    DDLogError(@"Error connecting: %@", error);
    self.viewController.label.text = @"Oops";
}
else
{
    DDLogVerbose(@"Connecting...");
}


- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
    DDLogInfo(@"socket:%p didConnectToHost:%@ port:%hu", sock, host, port);
    self.viewController.label.text = @"Connected";

    // We're just going to send a test string to the server.

    NSString *myStr = @"testing...123...\r\n";
    NSData *myData = [myStr dataUsingEncoding:NSUTF8StringEncoding];

    [asyncSocket writeData:myData withTimeout:5.0 tag:0];
}

And can see my socket test server app receive the string

"testing...123...\r\n"

But when I then have my socket test server send a string back, I naively expected the didReadData delegate to execute

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag

Yet the cold hard reality forced me learn that until I call

[asyncSocket readDataWithTimeout:5.0 tag:0];

... the didReadData delegate will not get called.

OK, that's fine. I get it.

Reading up on the documentation some more it clearly states that

AsyncSocket is a RunLoop based TCP socket library.

So now I'm looking at this RunLoop thing, which in my view is like the Message loop in Microsoft Windows. Being that iOS is an event/msg driven architecture (just like Win32), then the default main thread I'm currently in obviously has it's own msg loop to handle events.

My confusion is now having iOS RunLoop seem like some separate entity to have to work with in getting GCDAsyncSocket to work properly.

When it states that its default set of run loop mode is NSDefaultRunLoopMode, which is in the main thread.

Confused yet?

So under Win32 my comm event handling code would look like this:

while( sCOMport.hCOMport != INVALID_HANDLE_VALUE )  // ...while the COM port is open...
{
    // Wait for an event to occur on the port.
    WaitCommEvent( sCOMport.hCOMport, &dwCommStatus, NULL );

It would of course be in its own thread (haven't gotten there yet using GCDAsyncSocket), but that would be its own "RunLoop" in a way.

How do I do the same using GCDAsyncSocket so that I'm not stuck in some polling loop filling the queue with [asyncSocket readDataWithTimeout] calls?

I feel we need better examples in using this library.

like image 788
Sebastian Dwornik Avatar asked Nov 03 '11 15:11

Sebastian Dwornik


2 Answers

Ok, I got this working in some fashion.

Let me know if this goes against certain 'best practices'.

- (void)socket:(GCDAsyncSocket *)sock didConnectToHost:(NSString *)host port:(UInt16)port
{
    // setup as normal...

    // then ...

    // Instigate the first read
    [asyncSocket readDataWithTimeout:-1 tag:0];

.
.
}

Then ... when data comes in...

- (void)socket:(GCDAsyncSocket *)sock didReadData:(NSData *)data withTag:(long)tag
{
    // Do whatever you need to do with the data ...
.
.
.
    // and at the end ...
.
.
    // Always keep a 'read' in the queue.
    [asyncSocket readDataWithTimeout:-1 tag:0];
}

This will give you the RunLoop operation without using timers or other constructs. And can be encompassed within its own thread. (that's still TBD though)

like image 182
Sebastian Dwornik Avatar answered Apr 07 '23 22:04

Sebastian Dwornik


I know this is an old question and already have an accepted answer but here is my solution that I have already use in one of my apps:

after connecting to a host run the following dispatch queue:

dispatch_queue_t alwaysReadQueue = dispatch_queue_create("com.cocoaasyncsocket.alwaysReadQueue", NULL);

dispatch_async(alwaysReadQueue, ^{
    while(![socket isDisconnected]) {
        [NSThread sleepForTimeInterval:5];
        [socket readDataWithTimeout:-1 tag:0];
    }
});

You could've use the same queue to send a heartbeat requests just to keep the connection alive.

like image 35
Rashed Addosary Avatar answered Apr 07 '23 22:04

Rashed Addosary