解释NetCore上c# TcpClient/TcpListener的奇怪行为
本文关键字:TcpListener NetCore TcpClient 解释 | 更新日期: 2023-09-27 18:14:53
我在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上测试过。
这个问题最终由。net Core团队在GitHub上回答。
长答案-见https://github.com/dotnet/corefx/issues/13114的最后评论
简短回答-
该行为是预期的,并且是经过设计的。
我已经使用了您的代码片段并创建了一个实际的可运行演示解决方案,但仍处于未回答的状态。
只需要一个小的改变就可以得到预期的行为。在您的服务器端代码中,您需要确保实际读取整个字符串。调整以下行:
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());
只要看一下演示解决方案的历史记录,你就会发现所有的变化都很小。