I believe the shutdown sequence is as follows (as described here):
The MSDN documentation (remarks section) reads:
When using a connection-oriented
Socket, always call theShutdownmethod before closing theSocket. This ensures that all data is sent and received on the connected socket before it is closed.
This seems to imply that if I use Shutdown(SocketShutdown.Both), any data that has not yet been received, may still be consumed. To test this:
- I continuously send data to the client (via
Sendin a separate thread). - The client executed
Shutdown(SocketShutdown.Both). - The
BeginReceivecallback on the server executes, however,EndReceivethrows an exception: An existing connection was forcibly closed by the remote host. This means that I am unable to receive the0return value and in turn callShutdown.
As requested, I've posted the Server side code below (it's wrapped in a Windows Form and it was created just as an experiment). In my test scenario I did not see the CLOSE_WAIT state in TCPView as I normally did without sending the continuous data. So potentially I've done something wrong and I'm interrupting the consequences incorrectly. In another experiment:
- Client connects to server.
- Client executes
Shutdown(SocketShutdown.Both). - Server receives shutdown acknowledgement and sends some data in response. Server also executes
Shutdown. - Client receives data from server but the next
BeginReceiveis not allowed: A request to send or receive data was disallowed because the socket had already been shut down in that direction with a previous shutdown call
In this scenario, I was still expecting a 0 return value from EndReceive to Close the socket. Does this mean that I should use Shutdown(SocketShutdown.Send) instead? If so, when should one use Shutdown(SocketShutdown.Both)?
Code from first experiment:
private TcpListener SocketListener { get; set; } private Socket ConnectedClient { get; set; } private bool serverShutdownRequested; private object shutdownLock = new object(); private struct SocketState { public Socket socket; public byte[] bytes; } private void ProcessIncoming(IAsyncResult ar) { var state = (SocketState)ar.AsyncState; // Exception thrown here when client executes Shutdown: var dataRead = state.socket.EndReceive(ar); if (dataRead > 0) { state.socket.BeginReceive(state.bytes, 0, state.bytes.Length, SocketFlags.None, ProcessIncoming, state); } else { lock (shutdownLock) { serverShutdownRequested = true; state.socket.Shutdown(SocketShutdown.Both); state.socket.Close(); state.socket.Dispose(); } } } private void Spam() { int i = 0; while (true) { lock (shutdownLock) { if (!serverShutdownRequested) { try { ConnectedClient.Send(Encoding.Default.GetBytes(i.ToString())); } catch { break; } ++i; } else { break; } } } } private void Listen() { while (true) { ConnectedClient = SocketListener.AcceptSocket(); var data = new SocketState(); data.bytes = new byte[1024]; data.socket = ConnectedClient; ConnectedClient.BeginReceive(data.bytes, 0, data.bytes.Length, SocketFlags.None, ProcessIncoming, data); serverShutdownRequested = false; new Thread(Spam).Start(); } } public ServerForm() { InitializeComponent(); var hostEntry = Dns.GetHostEntry("localhost"); var endPoint = new IPEndPoint(hostEntry.AddressList[0], 11000); SocketListener = new TcpListener(endPoint); SocketListener.Start(); new Thread(Listen).Start(); } 
Sendalso returns0during a shutdown sequence. If I update the server to take this into consideration, before my flag can get set in theSpammethod,EndReceivestill throws an exception. I suspect this all happens because theCLOSE_WAITsocket state is somehow skipped.