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

Я пытаюсь получить простой пример, работающий с 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:&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];
}

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

"тестирование...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, &dwCommStatus, NULL );

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

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

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


person Sebastian Dwornik    schedule 03.11.2011    source источник


Ответы (2)


Хорошо, у меня это работает каким-то образом.

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

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

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

person Sebastian Dwornik    schedule 03.11.2011
comment
Я действительно сделал то же самое. Просто ставьте слушателя в очередь снова и снова. Мне тоже очень интересно, так это или нет. Как и вы, я ожидал, что didReadData будет вызываться без необходимости что-либо делать. Разве это не вся причина для сокетов.... - person renevdkooi; 03.02.2013
comment
Я так запутался прямо сейчас, что мне приходится использовать всасывающий кластер, чтобы просто получать данные, когда они поступят. :С - person Rihards; 30.04.2013

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

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

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

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

person Rashed Addosary    schedule 31.03.2014
comment
Разве это не сильно удержит сокет и не предотвратит его освобождение? Я бы рекомендовал использовать while(![weakSocket isDisconnected]), а затем после сна использовать __strong GCDAsyncSocket* strongSocket = weakSocket;, затем проверить, является ли он нулевым, и сломать, если это так. Даже если вы используете слабую ссылку на сокет, условное [socket isDisconnected] приведет к НЕТ, которое при отрицании приведет к ДА, что означает, что ваша отправка никогда не завершится. Хотя могу ошибаться. - person Sandy Chapman; 01.04.2014