14

我使用 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;

}

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

感谢您阅读我的问题。

4

3 回答 3

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 永远不会真正关闭。一种解决方法是始终调用断开连接方法。至少它对我有用,一切似乎都很好。

如果有人知道为什么 SRWebSocket 是这样实现的,请对此答案发表评论并帮助我。

于 2013-03-21T14:18:57.577 回答
4

我认为这是一个错误。
调用 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;
    }

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

于 2013-06-19T07:48:31.933 回答
2

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

SRWebSocket 实例不在此处,因为这将在客户端收到关闭控制帧作为响应之前_disconnect关闭与服务器的 TCP 连接。实际上,这里的 ing 会在客户端甚至可以将自己的 Close 帧发送到服务器之前拆除 TCP 套接字,因为最终调用之前可以。服务器可能会足够优雅地响应,但它是不合格的,当事情以这种方式设置时,您将无法发送情境唯一的关闭代码。_disconnect_disconnect_pumpWritingcloseWithCode:

这在handleCloseWithData:

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

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

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

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

因为 readyState 在 的第一次迭代中发生了变化closeWithCode:,这一次它根本不会运行。

emp 的错误修复对于使这项工作按预期工作是必要的,但是:否则来自服务器的 Close 框架不会做任何事情。连接仍然会结束,但是很脏,因为服务器(已经发送和接收了它的帧)将在它的一端断开套接字,并且客户端将响应一个NSStreamEventEndEncountered:,这通常是为突然丢失的连接性。更好的方法是确定为什么框架永远不会超出_innerPumpScannerto handleCloseWIthData:。另一个要记住的问题是,默认情况下,close只调用closeWithCode:不符合 RFC 的代码 -1。这会在我的服务器上引发错误,直到我将其更改为发送一个接受的值。

所有这一切:您的委托方法不起作用,因为您在调用后立即取消设置委托close。里面的一切都在close一个异步块内;didCloseWithCode:无论您在这里做什么,在您调用时都不会有委托人可以调用。

于 2013-08-17T14:31:02.737 回答