WebSocket Connection未使用SocketRocket关闭

时间:2013-03-20 17:19:45

标签: objective-c connection 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;

}

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

感谢您阅读我的问题。

3 个答案:

答案 0 :(得分:4)

我发现代理永远不会被调用,因为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。

此布尔值仅在断开连接方法中设置一次。

- (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是这样实现的,请对此答案发表评论并帮助我。

答案 1 :(得分:4)

我认为这是一个错误 当呼叫关闭时,服务器回显回“关闭”消息 它由SRWebSocket接收,但是_selfRetain永远不会设置为nil,并且套接字保持打开状态(流不会关闭)并且我们有内存泄漏。
我也在测试聊天应用程序中检查并观察了这一点 我做了以下更改:

-(BOOL)_innerPumpScanner {    
    BOOL didWork = NO;

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

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

答案 2 :(得分:2)

  

一旦端点同时发送和接收了一个关闭控制帧,该端点应该按照第7.1.1节的定义关闭WebSocket连接。 (RFC 6455 7.1.2

此处的SRWebSocket实例不_disconnect,因为在客户端收到响应的关闭控制帧之前,将关闭与服务器的TCP连接。实际上,_disconnect在这里将拆除TCP套接字,然后客户端甚至可以将自己的关闭帧发送到服务器,因为_disconnect最终会在_pumpWriting之前调用closeWithCode: 。服务器可能会很好地响应,但它不合格,并且在设置这种方式时你将无法发送情境唯一的密码。

handleCloseWithData:

正确处理了这个问题
if (self.readyState == SR_OPEN) {
    [self closeWithCode:1000 reason:nil];
}
dispatch_async(_workQueue, ^{
    [self _disconnect];
});

此块处理客户端和服务器发起的关闭请求。如果服务器发送第一个关闭帧,则该方法按照您布置的序列运行,最终通过_pumpWriting结束closeWithCode:,客户端将使用自己的关闭帧进行响应。然后它继续拆除与_disconnect的连接。

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

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

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

然而,必须使用emp的错误修复才能使其按预期工作:否则服务器的关闭框架不会执行任何操作。连接仍然会结束,但很脏,因为服务器(同时发送和接收其帧)将在其端部分解套接字,客户端将以NSStreamEventEndEncountered:响应,这通常是为流错误保留的突然失去连接造成的。更好的方法是确定帧永远不会从_innerPumpScannerhandleCloseWIthData:的原因。要记住的另一个问题是,默认情况下,close仅使用RFC不符合代码-1调用closeWithCode:。这会在我的服务器上引发错误,直到我将其更改为发送一个可接受的值。

所有说法:您的委托方法不起作用,因为您在致电close后立即取消了委托。 close中的所有内容都位于异步块内;无论您在此处做什么,在您调用didCloseWithCode:时都不会留下代理人。