Соединение WebSocket не закрывается с помощью SocketRocket

Я использую библиотеку SocketRocket для Objective-C для подключения к websocket:

-(void)open {

if( self.webSocket ) {
    [self.webSocket close];
    self.webSocket.delegate = nil;
}

self.webSocket = [[SRWebSocket alloc] initWithURLRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:@"ws://192.168.0.254:5864"] cachePolicy:NSURLRequestUseProtocolCachePolicy timeoutInterval:20]];
self.webSocket.delegate = self;
[self.webSocket open];
}

открытие соединения работает полностью нормально. Делегат вызывается после установления соединения.

-(void)webSocketDidOpen:(SRWebSocket *)webSocket {

NSLog(@"WebSocket is open");

}

но когда я хочу, чтобы закрыть соединение, ничего не происходит.

-(void)close {

if( !self.webSocket )
    return;

[self.webSocket close];
self.webSocket.delegate = nil;

}

делегат для успешного закрытия соединения не называется. Кто-нибудь может сказать мне, почему это происходит?

Спасибо, что прочитали мой вопрос.

3 ответов


я понял, что делегат никогда не вызывается, потому что websocket никогда не закрывается. Закрытие websocket в SRWebSocket происходит в методе pumpWriting такой:

if (_closeWhenFinishedWriting && 
    _outputBuffer.length - _outputBufferOffset == 0 && 
    (_inputStream.streamStatus != NSStreamStatusNotOpen &&
     _inputStream.streamStatus != NSStreamStatusClosed) &&
    !_sentClose) {
    _sentClose = YES;

    [_outputStream close];
    [_inputStream close];

    if (!_failed) {
        dispatch_async(_callbackQueue, ^{
            if ([self.delegate respondsToSelector:@selector(webSocket:didCloseWithCode:reason:wasClean:)]) {
                [self.delegate webSocket:self didCloseWithCode:_closeCode reason:_closeReason wasClean:YES];
            }
        });
    }

    _selfRetain = nil;

    NSLog(@" Is really closed and released ");
}
else {

    NSLog(@" Is NOT closed and released ");
}

все потоки и объект для сохранения websocket закрываются или удаляются там. Пока они все еще открыты, сокет не будет закрыт соответствующим образом. Но закрытие никогда не происходило в моей программе, потому что когда я пытался закрыть websocket, _closeWhenFinishedWriting всегда было.

Это логическое значение устанавливается только один раз в отключить метод.

- (void)_disconnect;
{

assert(dispatch_get_current_queue() == _workQueue);
SRFastLog(@"Trying to disconnect");
_closeWhenFinishedWriting = YES;
[self _pumpWriting];

}

но при вызове closeWithCode метод в SRWebSocket,отключить вызывается только в одном случае, если websocket находится в состоянии подключения.

BOOL wasConnecting = self.readyState == SR_CONNECTING;

SRFastLog(@"Closing with code %d reason %@", code, reason);
dispatch_async(_workQueue, ^{

    if (wasConnecting) {
        [self _disconnect];
        return;
    }

это означает,что если сокет находится в другом состоянии, websocket никогда не закроется. Один обходной путь - всегда звоните отключить метод. По крайней мере, это сработало для меня, и все вроде бы хорошо.

Если у кого-то есть идея, почему SRWebSocket реализован так, пожалуйста, оставьте комментарий для этого ответа и помогите мне.


Я думаю, что это ошибка.
При вызове close сервер echo возвращает сообщение "закрыть".
Он получен SRWebSocket, однако _selfRetain никогда не устанавливается в ноль, а сокет остается открытым (потоки не закрыты), и у нас есть утечка памяти.
Я проверил и заметил это в тестовом приложении чата, а также.
Я внес следующее изменение:--6-->

-(BOOL)_innerPumpScanner {    
    BOOL didWork = NO;

    if (self.readyState >= SR_CLOSING) {
        [self _disconnect];  // <--- Added call to disconnect which releases _selfRetain
        return didWork;
    }

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


как только конечная точка отправила и получила фрейм управления Close, эта конечная точка должна закройте соединение WebSocket как указано в разделе 7.1.1. (RFC 6455 7.1.2)

экземпляр SRWebSocket не _disconnect здесь, потому что это закроет TCP-соединение с сервером до в ответ клиент получил фрейм Close control. На самом деле,_disconnecting здесь снесет TCP-сокет прежде чем клиент сможет отправить свой собственный кадр закрытия на сервер, потому что _disconnect в конечном счете называет _pumpWriting до closeWithCode: может. Сервер, вероятно, ответит достаточно изящно, но он не соответствует, и вы не сможете отправлять уникальные коды закрытия ситуации, пока все настроено таким образом.

это должным образом handleCloseWithData:

if (self.readyState == SR_OPEN) {
    [self closeWithCode:1000 reason:nil];
}
dispatch_async(_workQueue, ^{
    [self _disconnect];
});

этот блок обрабатывает запросы закрытия, инициированные как клиентом, так и сервером. Если сервер отправляет первый Закройте кадр, метод запускается в соответствии с последовательностью, которую вы выложили, в конечном итоге в _pumpWriting via closeWithCode:, где клиент будет отвечать своим собственным закрытым фреймом. Затем он продолжает разрушать связь с этим _disconnect.

когда клиент отправляет кадр первым,closeWithCode: выполняется один раз без закрытия TCP-соединения, потому что _closeWhenFinishedWriting все равно false. Это позволяет серверному времени отвечать своим собственным закрытым фреймом, который обычно приводит к запуску closeWithCode: снова, но для следующего блока в верхней части этого метода:

if (self.readyState == SR_CLOSING || self.readyState == SR_CLOSED) {
    return;
}

потому что readyState изменяется на первой итерации closeWithCode: на этот раз он просто не будет работать.

исправление ошибки emp необходимо, чтобы сделать эту работу по назначению, однако: в противном случае закрыть кадр с сервера ничего не делает. Соединение все равно закончится, но грязно, потому что сервер (отправив и получив свои кадры) сломает сокет на своем конце, а клиент ответим NSStreamEventEndEncountered:, который обычно зарезервирован для ошибок потока, вызванных внезапными потерями подключения. Лучшим подходом было бы определить, почему фрейм никогда не выходит из _innerPumpScanner to handleCloseWIthData:. Еще одна проблема, которую нужно иметь в виду, это по умолчанию,close просто называет closeWithCode: С RFC-несоответствующим кодом -1. Это вызвало ошибки на моем сервере, пока я не изменил его, чтобы отправить одно из принятых значений.

все, что сказал: ваш метод делегата не работает, потому что вы расстраиваете делегата сразу после вызова close. Все в close находится внутри асинхронного блока; к моменту вызова didCloseWithCode: независимо от того, что вы делаете здесь.