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であったため、プログラムで閉じが発生することはありませんでした。

このブール値は、disconnectメソッドで1回だけ設定されます。

- (void)_disconnect;
{

assert(dispatch_get_current_queue() == _workQueue);
SRFastLog(@"Trying to disconnect");
_closeWhenFinishedWriting = YES;
[self _pumpWriting];

}

ただし、 SRWebSocketでcloseWithCodeメソッドを呼び出す場合、disconnectが呼び出されるのは1つの場合、つまりWebSocketが接続状態の場合のみです。

BOOL wasConnecting = self.readyState == SR_CONNECTING;

SRFastLog(@"Closing with code %d reason %@", code, reason);
dispatch_async(_workQueue, ^{

    if (wasConnecting) {
        [self _disconnect];
        return;
    }

つまり、ソケットが別の状態にある場合、WebSocketが実際に閉じることはありません。回避策の1つは、常に切断メソッドを呼び出すことです。少なくともそれは私にとってはうまくいき、すべてが大丈夫のようです。

SRWebSocketがそのように実装されている理由を誰かが知っている場合は、この回答にコメントを残して、私を助けてください。

于 2013-03-21T14:18:57.577 に答える
4

これはバグだと思います。
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;
    }

これでソケットが閉じ、インスタンスが解放され、メモリ リークがなくなりました。
私が確信していない唯一のことは、この方法で閉じるときにデリゲートを呼び出す必要があるかどうかです。これを調べます。

于 2013-06-19T07:48:31.933 に答える
2

エンドポイントが Close コントロール フレームの送信と受信の両方を完了すると、そのエンドポイントは、セクション 7.1.1 で定義されているように、WebSocket 接続を閉じる必要があります。( RFC 6455 7.1.2 )

SRWebSocket インスタンスはここにはありません。これは、クライアントが応答で Close コントロール フレームを受信する_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 要求を処理します。サーバーが最初の Close フレームを送信すると、メソッドはレイアウトしたシーケンスに従って実行され、最終的に_pumpWritingviacloseWithCode:で終了し、クライアントは独自の Close フレームで応答します。その後、それとの接続を切断し_disconnectます。

クライアントが最初にフレームを送信すると、まだ false でcloseWithCode:あるため、TCP 接続を閉じずに 1 回実行されます。_closeWhenFinishedWritingこれにより、サーバーは独自の Close フレームで応答することができます。これにより、通常はcloseWithCode:再度実行されますが、そのメソッドの上部にある次のブロックについては、次のようになります。

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

readyState は の最初の繰り返しで変更されるため、closeWithCode:今回は単純に実行されません。

ただし、これを意図したとおりに機能させるには、emp のバグ修正が必要です。そうしないと、サーバーからの Close フレームは何もしません。接続は引き続き終了しますが、サーバー (フレームの送受信の両方を持つ) がソケットを切断し、クライアントが で応答するため、接続は終了しますNSStreamEventEndEncountered:。接続性。より良いアプローチは、フレームが から に出ない理由を特定すること_innerPumpScannerですhandleCloseWIthData:。心に留めておくべきもう 1 つの問題は、デフォルトでは、RFC に準拠しないコード -1 を使用してclose呼び出すだけであるということです。closeWithCode:これにより、受け入れられた値の1つを送信するように変更するまで、サーバーにエラーが発生しました。

つまり、 を呼び出した直後にデリゲートの設定を解除しているため、デリゲート メソッドは機能しませんclose。のすべてがcloseasync ブロック内にあります。didCloseWithCode:ここで他に何をしても、呼び出すまでにデリゲートを呼び出す必要はありません。

于 2013-08-17T14:31:02.737 に答える