Как мне получить didReadData внутри выполнения GCDAsyncSocket в текущем цикле выполнения?

#runloop #cocoaasyncsocket

#runloop #cocoaasyncsocket

Вопрос:

Я пытаюсь привести простой пример работы с GCDAsyncSocket и обнаруживаю, что мне не хватает определенных элементов понимания, и надеюсь, что вы, прекрасные люди, сможете помочь объяснить это.

Я настроил материал GCDAsyncSocket ниже:

 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:amp;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...rn";
    NSData *myData = [myStr dataUsingEncoding:NSUTF8StringEncoding];

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

И я вижу, что приложение моего тестового сервера сокетов получает строку

«тестирование … 123 … r n»

Но когда затем мой тестовый сервер сокета отправляет строку обратно, я наивно ожидал, что делегат didReadData выполнит

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

Тем не менее, суровая реальность заставила меня выучить это, пока я не позвоню

 [asyncSocket readDataWithTimeout:5.0 tag:0];
  

делегат didReadData вызываться не будет.

Хорошо, все в порядке. Я понял.

При чтении документации еще немного, в ней четко указано, что

AsyncSocket — это библиотека TCP-сокетов на основе RunLoop.

Итак, теперь я смотрю на эту вещь RunLoop, которая, на мой взгляд, похожа на цикл сообщений в Microsoft Windows. Поскольку iOS — это архитектура, управляемая событиями / сообщениями (как и Win32), то основной поток по умолчанию, в котором я сейчас нахожусь, очевидно, имеет собственный цикл сообщений для обработки событий.

Мое замешательство заключается в том, что теперь iOS RunLoop выглядит как какая-то отдельная сущность, с которой нужно работать, чтобы заставить GCDAsyncSocket работать должным образом.

Когда в нем указано, что его режимом цикла выполнения по умолчанию является NSDefaultRunLoopMode, который находится в основном потоке.

Еще не запутался?

Итак, в Win32 мой код обработки событий связи будет выглядеть следующим образом:

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

Конечно, это было бы в его собственном потоке (еще не добрался туда с помощью GCDAsyncSocket), но это было бы его собственным «циклом выполнения».

Как мне сделать то же самое, используя GCDAsyncSocket, чтобы я не застрял в каком-то цикле опроса, заполняющем очередь вызовами [AsyncSocket readDataWithTimeout]?

Я чувствую, что нам нужны лучшие примеры использования этой библиотеки.

Ответ №1:

Хорошо, я каким-то образом добился этого.

Дайте мне знать, если это противоречит определенным «лучшим практикам».

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

    // then ...

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

.
.
}
  

Затем… при поступлении данных…

 - (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];
}
  

Это даст вам выполнение цикла выполнения без использования таймеров или других конструкций. И может быть включен в его собственный поток. (хотя это все еще TBD)

Комментарии:

1. Я на самом деле сделал то же самое. Просто «повторно ставьте в очередь» слушателя снова и снова. Мне также очень любопытно, правильно ли это или нет. Как и вы, я бы ожидал, что didReadData будет вызываться без необходимости что-либо делать. Разве это не единственная причина для сокетов….

2. Я сейчас в таком замешательстве, что мне приходится использовать suck cluster, чтобы просто получать данные, когда они поступили. :S

Ответ №2:

Я знаю, что это старый вопрос, и у меня уже есть принятый ответ, но вот мое решение, которое я уже использовал в одном из своих приложений:

после подключения к хосту запустите следующую очередь отправки:

 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];
    }
});
  

Вы могли бы использовать ту же очередь для отправки запросов на сердцебиение, просто чтобы поддерживать соединение.

Комментарии:

1. Разве это не приведет к сильному сохранению сокета и предотвращению освобождения? Я бы рекомендовал использовать, while(![weakSocket isDisconnected]) а затем после перехода в спящий __strong GCDAsyncSocket* strongSocket = weakSocket; режим, затем проверять, равно ли оно нулю, и прерывать, если это так. Даже если вы используете слабую ссылку на сокет, условие [socket isDisconnected] приведет к NO, которое при отрицании приведет к YES, что означает, что ваша отправка никогда не завершится. Однако я могу ошибаться.