解释 C# TcpClient/TcpListener 在 Net Core 上的怪异行为

Explain weird behavior of C# TcpClient/TcpListener on NetCore

我 运行 在 Windows 10 上使用 NetCore,我有两个程序 - 服务器和客户端,我都在本地 运行 使用。

执行顺序如下:

此序列使用以下代码从服务器生成以下输出:

Waiting for client.
Reading message.
Incoming message: This message is longer than what
Sending message.
Closing connection.
Waiting for client.
Reading message.
Incoming message: This message is longer than what
Sending message.
Closing connection.
Waiting for client.

以及客户端的以下输出:

Connecting to server.
Sending message.
Reading message.
Incoming message: Thank you!


Connecting to server.
Sending message.
Reading message.

Unhandled Exception: System.AggregateException: One or more errors occurred. (Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host.) ---> System.IO.IOException: Unable to read data from the transport connection: An existing connection was forcibly closed by the remote host. ---> System.Net.Sockets.SocketException: An existing connection was forcibly closed by the remote host

换句话说 - 运行 客户端的第二次尝试以异常结束。那是奇怪的部分。

这是我用于重现问题的最小代码示例。

服务器代码:

public static async Task StartServerAsync()
{
  TcpListener listener = new TcpListener(IPAddress.Any, 1234);
  listener.Server.NoDelay = true;
  listener.Server.LingerState = new LingerOption(true, 0);
  listener.Start();

  while (true)
  {
    Console.WriteLine("Waiting for client.");
    using (TcpClient client = await listener.AcceptTcpClientAsync())
    {
      client.NoDelay = true;
      client.LingerState = new LingerOption(true, 0);

      Console.WriteLine("Reading message.");
      using (NetworkStream stream = client.GetStream())
      {
        byte[] buffer = new byte[32];
        int len = await stream.ReadAsync(buffer, 0, buffer.Length);
        string incomingMessage = Encoding.UTF8.GetString(buffer, 0, len);
        Console.WriteLine("Incoming message: {0}", incomingMessage);


        Console.WriteLine("Sending message.");
        byte[] message = Encoding.UTF8.GetBytes("Thank you!");
        await stream.WriteAsync(message, 0, message.Length);
        Console.WriteLine("Closing connection.");
      }
    }
  }
}

客户代码:

public static async Task StartClientAsync()
{
  using (TcpClient client = new TcpClient())
  {
    client.NoDelay = true;
    client.LingerState = new LingerOption(true, 0);

    Console.WriteLine("Connecting to server.");
    await client.ConnectAsync("127.0.0.1", 1234);

    Console.WriteLine("Sending message.");
    using (NetworkStream stream = client.GetStream())
    {
      byte[] buffer = Encoding.UTF8.GetBytes("This message is longer than what the server is willing to read.");
      await stream.WriteAsync(buffer, 0, buffer.Length);

      Console.WriteLine("Reading message.");
      int len = await stream.ReadAsync(buffer, 0, buffer.Length);
      string message = Encoding.UTF8.GetString(buffer, 0, len);

      Console.WriteLine("Incoming message: {0}", message);
    }
  }
}

奇怪的是,如果我们将客户端的消息从

"This message is longer than what the server is willing to read."

"This message is short."

客户端的两个实例在没有崩溃的情况下完成并产生相同的输出。

请注意,如果我省略了设置 LingerState 的所有三行,则没有任何区别。如果我将 LingerState 设置为

,行为也没有区别

new LingerState(true, 5)

如果我将NoDelay 设置为false 也没有区别。换句话说,在任一侧为 LingerState 和 NoDelay 设置任何值似乎对崩溃没有任何影响。防止崩溃的唯一方法是在服务器端从客户端读取整个输入。

这很奇怪,我想知道是否有人可以解释一下。我不确定它是否也适用于 .NET Framework,仅在 NetCore 1.0.0 和 Windows 10.

上进行了测试

我已经获取了您的代码片段并创建了一个实际的可运行文件 demo solution 仍处于未答复状态。

只需稍作改动即可获得预期的行为。在您的服务器端代码中,您需要确保实际读取整个字符串。调整以下行:

byte[] buffer = new byte[32];
int len = await stream.ReadAsync(buffer, 0, buffer.Length);
string incomingMessage = Encoding.UTF8.GetString(buffer, 0, len);
Console.WriteLine("Incoming message: {0}", incomingMessage);

与:

StringBuilder sb = new StringBuilder();
byte[] buffer = new byte[32];
int len;
do
{
    len = await stream.ReadAsync(buffer, 0, buffer.Length);
    sb.Append(Encoding.UTF8.GetString(buffer, 0, len));
} while (len == buffer.Length);
Console.WriteLine("Incoming message: {0}", sb.ToString());

看看 the demo solution 的历史,你会发现所有的变化都是最小的。

.NET Core 团队终于在 GitHub 上回答了这个问题。

长答案 - 请参阅 https://github.com/dotnet/corefx/issues/13114

的最后一条评论

简答 -

The behavior is expected, and is by-design.