11

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();
}
4

2 回答 2

2

Shutdown(SocketShutdown.Both) 禁用当前套接字上的发送和接收操作。调用 Shutdown(SocketShutdown.Both) 是客户端与服务器的实际断开连接。您可以通过检查服务器端 SocketState 对象中的套接字 Connected 属性来看到这一点:它将是错误的。

发生这种情况是因为 Shutdown 操作是不可逆的,因此在停止套接字上的发送和接收之后,由于它是隔离的,因此保持连接是没有意义的。

“一旦调用关闭函数来禁用发送、接收或两者,就没有方法可以重新启用现有套接字连接的发送或接收。” (https://docs.microsoft.com/en-us/windows/win32/api/winsock/nf-winsock-shutdown

至于你的问题:

  • 我不断地向客户端发送数据(通过在单独的线程中发送)。
  • 客户端执行了 Shutdown(SocketShutdown.Both)。--> 这会断开客户端
  • 服务端的 BeginReceive 回调执行,但是 EndReceive 抛出异常:一个现有的连接被远程主机强行关闭。这意味着我无法接收 0 返回值并反过来调用 Shutdown。

EndReceive 抛出异常,因为客户端套接字不再连接。

优雅地终止套接字:

  1. 客户端套接字调用 Shutdown(SocketShutdown.Send)) 但应继续接收

  2. 在服务器上,EndReceive 返回读取的 0 字节(客户端发出信号,表示其一侧没有更多数据)

  3. 服务器 A) 发送它的最后一个数据 B) 调用 Shutdown(SocketShutdown.Send)) C) 在套接字上调用 Close,可以选择超时以允许从客户端读取数据

  4. 客户端 A)从服务器读取剩余的数据,然后接收 0 字节(服务器表示没有来自它的更多数据) B)在套接字上调用 Close

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

于 2020-06-25T00:06:32.990 回答
0

Shutdown(SocketShutdown.Both)当您不想接收或发送时应该使用。您要么想突然关闭连接,要么知道对方已使用SocketShutdown.Receive. 例如,您有一个时间服务器,它将当前时间发送给连接它的客户端,服务器发送时间并调用Shutdown(SocketShutdown.Received),因为它不期望来自客户端的更多数据。客户端在收到时间数据后应调用 Shutdown(SocketShutdown.Both),因为它不会发送或接收任何进一步的数据。

于 2020-05-28T20:46:49.077 回答