Соединение 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. На самом деле,_disconnect
ing здесь снесет 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:
независимо от того, что вы делаете здесь.