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 theShutdown
method 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
Send
in a separate thread). - The client executed
Shutdown(SocketShutdown.Both)
. - The
BeginReceive
callback on the server executes, however,EndReceive
throws an exception: An existing connection was forcibly closed by the remote host. This means that I am unable to receive the0
return 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
BeginReceive
is 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();
}
Send
also returns0
during a shutdown sequence. If I update the server to take this into consideration, before my flag can get set in theSpam
method,EndReceive
still throws an exception. I suspect this all happens because theCLOSE_WAIT
socket state is somehow skipped. – Centennial