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

Соединение с 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;

}

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

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

4b9b3361

Ответ 1

Я понял, что делегат никогда не называется, потому что веб-сайт никогда не закрывается. Закрытие 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 реализована так, пожалуйста, оставьте комментарий для этого ответа и помогите мне.

Ответ 2

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

-(BOOL)_innerPumpScanner {    
    BOOL didWork = NO;

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

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

Ответ 3

Как только конечная точка отправила и получила контрольный кадр Close, эта конечная точка ДОЛЖНА закрыть соединение WebSocket, как определено в разделе 7.1.1. (RFC 6455 7.1.2)

Экземпляр SRWebSocket здесь не _disconnect, потому что это закроет TCP-соединение с сервером до того, как клиент получит ответный флажок Close. Фактически, _disconnect ing здесь будет разорвать TCP-сокет, прежде чем клиент сможет даже отправить собственный сервер Close на сервер, потому что _disconnect в конечном итоге вызывает _pumpWriting до closeWithCode:. Вероятно, сервер будет достаточно изящным образом реагировать, но он несоответствует, и вы не сможете отправлять уникальные коды с уникальными условиями, когда все будет настроено таким образом.

Это правильно рассмотрено в handleCloseWithData:

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

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

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

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

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

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

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