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

2

7

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.

Highchair answered 3/11, 2011 at 15:7 Comment(0)
H
19

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)

Highchair answered 3/11, 2011 at 16:2 Comment(2)
I actually did the same thing. Just "re-queue" the listener again and again. I as well am pretty curious if this is the way to go or not. Like you I would have expected didReadData to be called without needing to do anything. Isn't that the whole reason for sockets....Zischke
I'm so confused right now that I've to use suck cluster to just receive data when they have arrived. :SBlameful
D
1

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.

Depredation answered 31/3, 2014 at 9:47 Comment(1)
Is this not going to make a strong retain on the socket and prevent deallocation? I'd recommend using while(![weakSocket isDisconnected]) and then after the sleep using __strong GCDAsyncSocket* strongSocket = weakSocket; then checking if it's nil and breaking if it is. Even if you use a weak reference to socket, the conditional [socket isDisconnected] will result in NO which when negated will result in YES meaning your dispatch will never exit. I could be mistaken though.Servile

© 2022 - 2024 — McMap. All rights reserved.