WebSocket连接没有使用SocketRocket关闭



我使用Objective-C的SocketRocket库来连接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;
}

未调用用于成功关闭连接的委托。有人能告诉我为什么会这样吗?

感谢您阅读我的问题

我发现委托从未被调用,因为websocket从未真正关闭。SRWebSocket中的websocket关闭发生在方法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总是NO。

这个布尔值只在disconnect方法中设置一次。

- (void)_disconnect;
{
assert(dispatch_get_current_queue() == _workQueue);
SRFastLog(@"Trying to disconnect");
_closeWhenFinishedWriting = YES;
[self _pumpWriting];
}

但是当在SRWebSocket中调用closeWithCode方法时,disconnect只在一种情况下被调用,那就是,如果websocket处于连接状态。

BOOL wasConnecting = self.readyState == SR_CONNECTING;
SRFastLog(@"Closing with code %d reason %@", code, reason);
dispatch_async(_workQueue, ^{
    if (wasConnecting) {
        [self _disconnect];
        return;
    }

这意味着,如果套接字处于另一种状态,websocket将永远不会真正关闭。一种解决方法是始终调用disconnect方法。至少它对我有效,一切似乎都很好。

如果有人有一个想法,为什么SRWebSocket是这样实现的,请为这个答案留下评论并帮助我。

我认为这是一个bug。
当调用close时,服务器回显'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;
    }

现在套接字关闭,实例被释放,内存泄漏也消失了。
我唯一不确定的是当以这种方式关闭时是否应该调用委托。我会调查的。

一旦终端发送和接收了一个关闭控制帧,终端应该关闭WebSocket连接,如章节7.1.1所定义的。(RFC 6455 7.1.2)

SRWebSocket实例在这里没有_disconnect,因为这会在客户端收到close控制帧响应之前关闭与服务器的TCP连接。实际上,在客户端甚至可以向服务器发送自己的Close帧之前,_disconnect在这里将会破坏TCP套接字,因为_disconnect最终会在closeWithCode:之前调用_pumpWriting。服务器可能会很优雅地响应,但这是不符合标准的,并且当以这种方式设置时,您将无法发送唯一的关闭代码。

这在handleCloseWithData:

中得到了适当的处理
if (self.readyState == SR_OPEN) {
    [self closeWithCode:1000 reason:nil];
}
dispatch_async(_workQueue, ^{
    [self _disconnect];
});

这个块处理由客户端和服务器端发起的关闭请求。如果服务器发送第一个Close帧,该方法按照您设置的顺序运行,最终在_pumpWriting通过closeWithCode:结束,客户端将用自己的Close帧响应。然后,它继续断开与该_disconnect的连接。

当客户端先发送帧时,closeWithCode:运行一次而不关闭TCP连接,因为_closeWhenFinishedWriting仍然为假。这允许服务器有时间用自己的Close帧响应,这通常会导致再次运行closeWithCode:,但对于该方法顶部的以下块:

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

因为readyState是在closeWithCode:的第一次迭代时更改的,所以这次它根本不会运行。

然而,

emp的错误修复是必要的,否则来自服务器的Close帧不会做任何事情。连接仍然会结束,但是很脏,因为服务器(已经发送和接收了它的帧)将在它的端中断套接字,客户端将响应NSStreamEventEndEncountered:,这通常是为突然失去连接而导致的流错误保留的。更好的方法是确定为什么帧永远不会从_innerPumpScannerhandleCloseWIthData:。另一个需要记住的问题是,默认情况下,close只调用closeWithCode:,并带有一个不符合rfc的代码-1。这会在我的服务器上抛出错误,直到我将其更改为发送可接受的值之一。

所有这些都说:你的委托方法不起作用,因为你在调用close之后取消了委托。close中的所有内容都在异步块中;当你调用didCloseWithCode:时,无论你在这里做什么,都不会有一个委托留给你调用。

相关内容

  • 没有找到相关文章

最新更新