Socket Shutdown: when should I use SocketShutdown.Both
Asked Answered
C

2

12

I believe the shutdown sequence is as follows (as described here):

Socket Shutdown sequence

The MSDN documentation (remarks section) reads:

When using a connection-oriented Socket, always call the Shutdown method before closing the Socket. 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 the 0 return value and in turn call Shutdown.

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();
}
Centennial answered 4/9, 2015 at 11:50 Comment(2)
Send also returns 0 during a shutdown sequence. If I update the server to take this into consideration, before my flag can get set in the Spam method, EndReceive still throws an exception. I suspect this all happens because the CLOSE_WAIT socket state is somehow skipped.Centennial
did you ever find an answer for this?Nectareous
P
2

Shutdown(SocketShutdown.Both) disables both the send and receive operations on the current socket. Calling Shutdown(SocketShutdown.Both) is an actual disconnection of your client from the server. You can see this by checking the socket Connected property in your SocketState object on the server side: it will be false.

This happens because the Shutdown operation is not reversible, so after stopping both send and receive on the socket, there's no point in keeping it connected as it is isolated.

"Once the shutdown function is called to disable send, receive, or both, there is no method to re-enable send or receive for the existing socket connection." (https://learn.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-shutdown)

As for your question:

  • I continuously send data to the client (via Send in a separate thread).
  • The client executed Shutdown(SocketShutdown.Both). --> this disconnects the client
  • 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 the 0 return value and in turn call Shutdown.

EndReceive throws an exception because the client socket is not connected anymore.

To gracefully terminate the socket:

  1. the client socket calls Shutdown(SocketShutdown.Send)) but should keep receiving

  2. on the server, EndReceive returns 0 bytes read (the client signals there is no more data from its side)

  3. the server A) sends its last data B) calls Shutdown(SocketShutdown.Send)) C) calls Close on the socket, optionally with a timeout to allow the data to be read from the client

  4. the client A) reads the remaining data from the server and then receives 0 bytes (the server signals there is no more data from its side) B) calls Close on the socket

(https://learn.microsoft.com/it-it/windows/win32/winsock/graceful-shutdown-linger-options-and-socket-closure-2?redirectedfrom=MSDN)

Piotr answered 25/6, 2020 at 0:6 Comment(0)
D
0

Shutdown(SocketShutdown.Both) should be used when you don't want to receive or send. You either want to abruptly close connection or you know that other party has shutdown using SocketShutdown.Receive. For example, you have a time server that sends current time to the client that connects it, server sends time and calls Shutdown(SocketShutdown.Received) as it is not expecting any more data from client. The client upon receiving time data should call Shutdown(SocketShutdown.Both) as it is not going to send or receive any further data.

Dialectologist answered 28/5, 2020 at 20:46 Comment(1)
Yes, but when the users tries to repeat the operation 10 seconds later you get the error "A request to send or receive data has failed because the socket has already been shutdown" I'm very disappointed with socket implementation in C#Leonteen

© 2022 - 2024 — McMap. All rights reserved.