Once an endpoint has both sent and received a Close control frame, that endpoint SHOULD Close the WebSocket Connection as defined in Section 7.1.1. (RFC 6455 7.1.2)
The SRWebSocket instance doesn't _disconnect
here because that would close the TCP connection to the server before the client has received a Close control frame in response. In fact, _disconnect
ing here will tear down the TCP socket before the client can even send its own Close frame to the server, because _disconnect
ultimately calls _pumpWriting
before closeWithCode:
can. The server will probably respond gracefully enough, but it's nonconforming, and you won't be able to send situation-unique close codes while things are set up this way.
This is properly dealt with in handleCloseWithData:
if (self.readyState == SR_OPEN) {
[self closeWithCode:1000 reason:nil];
}
dispatch_async(_workQueue, ^{
[self _disconnect];
});
This block handles Close requests initiated by both the client and the server. If the server sends the first Close frame, the method runs as per the sequence you laid out, ultimately ending up in _pumpWriting
via closeWithCode:
, where the client will respond with its own Close frame. It then goes on to tear down the connection with that _disconnect
.
When the client sends the frame first, closeWithCode:
runs once without closing the TCP connection because _closeWhenFinishedWriting
is still false. This allows the server time to respond with its own Close frame, which would normally result in running closeWithCode:
again, but for the following block at the top of that method:
if (self.readyState == SR_CLOSING || self.readyState == SR_CLOSED) {
return;
}
Because the readyState is changed on the first iteration of closeWithCode:
, this time it simply won't run.
emp's bug fix is necessary to make this work as intended, however: otherwise the Close frame from the server doesn't do anything. The connection will still end, but dirtily, because the server (having both sent and received its frames) will break down the socket on its end, and the client will respond with an NSStreamEventEndEncountered:
, which is normally reserved for stream errors caused by sudden losses of connectivity. A better approach would be to determine why the frame never gets out of _innerPumpScanner
to handleCloseWIthData:
. Another issue to keep in mind is that by default, close
just calls closeWithCode:
with an RFC-nonconforming code of -1. This threw errors on my server until I changed it to send one of the accepted values.
All that said: your delegate method doesn't work because you're unsetting the delegate right after you call close
. Everything in close
is inside an async block; there won't be a delegate left to call by the time you invoke didCloseWithCode:
regardless of what else you do here.