2013-03-20 10 views
14

Io uso la biblioteca SocketRocket per Objective-C per connettersi a un websocket:WebSocket connessione non è di chiusura utilizzando SocketRocket

-(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]; 
} 

Apertura del collegamento funziona tutto bene. Il delegato viene chiamato dopo aver stabilito la connessione.

-(void)webSocketDidOpen:(SRWebSocket *)webSocket { 

NSLog(@"WebSocket is open"); 

} 

Ma quando voglio chiudere la connessione, non succede nulla.

-(void)close { 

if(!self.webSocket) 
    return; 

[self.webSocket close]; 
self.webSocket.delegate = nil; 

} 

Il delegato per chiudere correttamente la connessione non viene chiamato. Qualcuno può dirmi perché questo succede?

Grazie per aver letto la mia domanda.

+0

cosa restituisce questa condizione "if (! Self.webSocket)"? –

+0

significa che il websocket non può essere chiuso se è nullo, perché non è comunque aperto –

+0

Ricevo sempre questo errore e Test to Speech interruzione audio, WebSocket chiuso con motivo [1000]: Terminazione manuale socket connessione Qualche suggerimento? –

risposta

4

Ho capito che il delegato non viene mai chiamato, perché la websocket non è mai veramente chiusa. La chiusura del websocket nel SRWebSocket avviene nel metodo pumpWriting simili:

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 "); 
} 

Tutti i flussi e un oggetto per mantenere la websocket sono chiuse o non cancellato. Finché sono ancora aperti, il socket non verrà chiuso in modo appropriato. Ma la chiusura non è mai avvenuta nel mio programma, perché quando ho provato a chiudere il web socket, _closeWhenFinishedWriting era sempre NO.

Questo valore booleano viene impostato una sola volta nel metodo di disconnessione.

- (void)_disconnect; 
{ 

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

} 

Ma quando si chiama il metodocloseWithCode in SRWebSocket, scollegare viene chiamato solo in un caso e cioè se il websocket è nello stato di collegamento.

BOOL wasConnecting = self.readyState == SR_CONNECTING; 

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

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

Ciò significa che se il socket si trova in un altro stato, il web socket non si chiuderà mai realmente. Una soluzione alternativa è chiamare sempre il metodo disconnettere. Almeno ha funzionato per me e tutto sembra andare bene.

Se qualcuno ha un'idea, perché SRWebSocket è implementato in questo modo, si prega di lasciare un commento per questa risposta e darmi una mano.

+0

Non trovo il metodo di disconnessione da nessuna parte nell'intero codice di SRWebSocket –

+0

Ricevo sempre questo errore e Test to Speech interruzione audio, WebSocket chiuso con motivo [1000]: interruzione manuale della connessione socket Qualche suggerimento? –

4

Penso che questo sia un bug.
Quando si chiama close, il server echo ritorna il messaggio 'close'.
Viene ricevuto da SRWebSocket, tuttavia _selfRetain non viene mai impostato su zero e il socket rimane aperto (i flussi non sono chiusi) e si verifica una perdita di memoria.
Ho controllato e osservato questo anche nell'app di chat di test.
ho fatto la seguente modifica:

-(BOOL)_innerPumpScanner {  
    BOOL didWork = NO; 

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

Ora la presa si chiude, l'istanza viene rilasciato, e la perdita di memoria è andata.
L'unica cosa di cui non sono sicuro è se il delegato debba essere chiamato quando si chiude in questo modo. Esaminerà questo

+0

Sì, hai ragione. Come ho accennato nella mia risposta, è necessario chiamare _disconnect per impostare BOOL _closeWhenFinishedWriting su YES, in modo che _selfRetain possa essere impostato su nil in modo appropriato –

+0

corretto. C'è il caso in cui un server muore, il delegato non verrà avvisato e siederà semplicemente lì. Ho apportato delle modifiche in modo che il delegato venga informato. Ho creato una [richiesta pull] (https://github.com/square/SocketRocket/pull/106) se qualcuno vuole vedere le modifiche che ho apportato. – emp

+0

Questo problema è stato l'indirizzo su GitHub, sebbene la correzione non sia stata unita. https://github.com/mediumbear/SocketRocket/commit/08afedbeb991edfda13aa675b5720c53ffaf7ef8 –

2

Dopo un endpoint ha sia inviato e ricevuto un primo frame di controllo, che endpoint DEVE chiudere la connessione WebSocket come definito nella sezione 7.1.1. (RFC 6455 7.1.2)

L'istanza SRWebSocket no _disconnect qui perché ciò chiudere la connessione TCP al server prima il cliente ha ricevuto un primo frame di controllo in risposta. In effetti, _disconnect in questo articolo abbatterà il socket TCP prima che il client possa persino inviare il proprio frame di chiusura al server, perché _disconnect in definitiva chiama _pumpWriting prima di closeWithCode: can. Il server probabilmente risponderà con sufficiente garbo, ma non è conforme e non sarà possibile inviare codici di chiusura univoci, mentre le cose sono impostate in questo modo.

questo è correttamente trattati in handleCloseWithData:

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

Questo blocco gestisce Chiudere Richieste avviate sia dal client e il server. Se il server invia il primo frame di chiusura, il metodo viene eseguito secondo la sequenza che hai disposto, terminando infine in _pumpWriting tramite closeWithCode:, in cui il client risponderà con il proprio frame di chiusura. Quindi continua a ridurre la connessione con quello _disconnect.

Quando il client invia il frame per primo, closeWithCode: viene eseguito una volta senza chiudere la connessione TCP perché _closeWhenFinishedWriting è ancora falso. Ciò consente l'ora del server di rispondere con un proprio Chiudi frame, che normalmente risultare in esecuzione closeWithCode: nuovamente, ma per il seguente blocco nella parte superiore di tale metodo:

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

Poiché il readyState è cambiata nel primo iterazione di closeWithCode:, questa volta semplicemente non verrà eseguito.

La correzione dei bug di emp è necessaria per fare in modo che tutto funzioni come previsto, altrimenti il ​​riquadro Chiudi dal server non esegue nulla. La connessione finirà comunque, ma in modo impervio, perché il server (avendo entrambi inviato e ricevuto i suoi frame) interromperà il socket alla sua estremità, e il client risponderà con uno NSStreamEventEndEncountered:, che è normalmente riservato agli errori di streaming causati da perdite improvvise di connettività. Un approccio migliore sarebbe determinare il motivo per cui il frame non esce mai da _innerPumpScanner a handleCloseWIthData:. Un altro problema da tenere a mente è che per impostazione predefinita, close chiama semplicemente closeWithCode: con un codice di non conformità RFC di -1. Questo ha gettato errori sul mio server fino a quando non l'ho cambiato per inviare uno dei valori accettati.

Tutto ciò che ha detto: il metodo delegato non funziona perché si sta disattivando il delegato subito dopo aver chiamato close. Tutto in close si trova all'interno di un blocco asincrono; non ci sarà un delegato rimasto da chiamare entro il momento in cui invochi il numero didCloseWithCode: indipendentemente da cos'altro fai qui.

+0

Ricevo sempre questo errore e Test to Speech interruzione audio, WebSocket chiuso con motivo [1000]: Connessione socket terminata manualmente Qualche suggerimento? –