解释 C# TcpClient/TcpListener 在 NetCore 上的奇怪行为

3
我正在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);
    }
  }
}

有趣的是,如果我们将客户端消息从

改为标签后,服务器不会接收到消息。这是因为HTTP协议要求请求消息必须以两个连续的新行作为结束符,而更改了消息内容后未正确结束消息。

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

to

"This message is short."

两个客户端实例都不会崩溃并且产生相同的输出。

请注意,如果我省略了设置LingerState的三行代码,则没有任何区别。如果我将LingerState设置为new LingerState(true, 5),则行为没有任何区别。

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

这很奇怪,我想知道是否有人能够解释一下。我不确定它是否也适用于.NET Framework,只测试过NetCore 1.0.0和Windows 10。

2个回答

3

这个问题最终由.NET Core团队在GitHub上得到了解答。

长篇回答 - 请参考https://github.com/dotnet/corefx/issues/13114的最后一条评论。

简短回答 -

这种行为是预期的,并且是设计的。


-1

我已经使用您的代码片段创建了一个实际可运行的演示解决方案,但仍处于未回答状态。

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

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

只需看一下演示解决方案的历史记录,你就会发现所有的更改都是微不足道的。

抱歉,问题中已经写明“防止崩溃的唯一方法是在服务器端读取来自客户端的所有输入。” 问题本身是关于为什么不读取整个消息时会导致程序崩溃。 - Wapac

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接