我试图创建一个 UDP 服务器,可以发送消息给所有客户端发送消息给它。实际情况稍微复杂一些,但是最简单的想象是把它想象成一个聊天服务器: 在此之前发送过消息的每个人都会收到其他客户机发送的所有消息。
所有这些都是通过 UdpClient
在不同的进程中完成的。(所有的网络连接都在同一个系统中,所以我没有 好好想想,UDP 的不可靠性在这里是一个问题。)
服务器代码是这样一个循环(稍后是完整代码) :
var udpClient = new UdpClient(9001);
while (true)
{
var packet = await udpClient.ReceiveAsync();
// Send to clients who've previously sent messages here
}
客户机代码也很简单——再次声明,这段代码略微缩写,但稍后将提供完整的代码:
var client = new UdpClient();
client.Connect("127.0.0.1", 9001);
await client.SendAsync(Encoding.UTF8.GetBytes("Hello"));
await Task.Delay(TimeSpan.FromSeconds(15));
await client.SendAsync(Encoding.UTF8.GetBytes("Goodbye"));
client.Close();
在其中一个客户端关闭其 UdpClient
(或进程退出)之前,这一切都工作正常。
下一次另一个客户机发送消息时,服务器将尝试将该消息传播到现已关闭的原始客户机。对此的 SendAsync
调用不会失败——但是当服务器循环回到 ReceiveAsync
时,那个异常失败,我还没有找到恢复的方法。
如果我从来没有向客户端发送断开连接的消息,我就不会看到问题。在此基础上,我还创建了一个“立即失败”的复制,只是发送到一个端点假定没有监听,然后尝试接收。同样的异常也会导致失败。
例外:
System.Net.Sockets.SocketException (10054): An existing connection was forcibly closed by the remote host.
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.CreateException(SocketError error, Boolean forAsyncThrow)
at System.Net.Sockets.Socket.AwaitableSocketAsyncEventArgs.ReceiveFromAsync(Socket socket, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.ReceiveFromAsync(Memory`1 buffer, SocketFlags socketFlags, EndPoint remoteEndPoint, CancellationToken cancellationToken)
at System.Net.Sockets.Socket.ReceiveFromAsync(ArraySegment`1 buffer, SocketFlags socketFlags, EndPoint remoteEndPoint)
at System.Net.Sockets.UdpClient.ReceiveAsync()
at Program.<Main>$(String[] args) in [...]
环境:
这是预料之中的行为吗?我是否在服务器端错误地使用了 UdpClient
?我可以接受客户端在关闭 UdpClient
后没有收到消息(这是可以预料的) ,在“完整”应用程序中,我会整理我的内部状态,以跟踪“活动”客户端(最近发送了数据包) ,但我不希望一个客户端关闭 UdpClient
导致整个服务器瘫痪。在一个控制台中运行服务器,在另一个控制台中运行客户机。一旦客户端完成了一次,再次运行它(以便它尝试发送到现在已经不存在的原始客户端)。
默认的.NET6控制台应用程序项目模板适用于所有项目。
复制代码
最简单的例子首先出现——但是如果您想运行一个服务器和客户机,它们会在之后显示。
故意破坏服务器(立即失败)
基于这样一种假设,即问题确实是由发送方造成的,因此只需要几行代码就可以很容易地重现这一点:
using System.Net;
using System.Net.Sockets;
var badEndpoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 12346);
var udpClient = new UdpClient(12345);
await udpClient.SendAsync(new byte[10], badEndpoint);
await udpClient.ReceiveAsync();
服务器
using System.Net;
using System.Net.Sockets;
using System.Text;
var udpClient = new UdpClient(9001);
var endpoints = new HashSet<IPEndPoint>();
try
{
while (true)
{
Log($"{DateTime.UtcNow:HH:mm:ss.fff}: Waiting to receive packet");
var packet = await udpClient.ReceiveAsync();
var buffer = packet.Buffer;
var clientEndpoint = packet.RemoteEndPoint;
endpoints.Add(clientEndpoint);
Log($"Received {buffer.Length} bytes from {clientEndpoint}: {Encoding.UTF8.GetString(buffer)}");
foreach (var otherEndpoint in endpoints)
{
if (!otherEndpoint.Equals(clientEndpoint))
{
await udpClient.SendAsync(buffer, otherEndpoint);
}
}
}
}
catch (Exception e)
{
Log($"Failed: {e}");
}
void Log(string message) =>
Console.WriteLine($"{DateTime.UtcNow:HH:mm:ss.fff}: {message}");
客户
(我以前有一个循环实际上接收服务器发送的数据包,但这似乎没有任何区别,所以为了简单起见,我删除了它。)
using System.Net.Sockets;
using System.Text;
Guid clientId = Guid.NewGuid();
var client = new UdpClient();
Log("Connecting UdpClient");
client.Connect("127.0.0.1", 9001);
await client.SendAsync(Encoding.UTF8.GetBytes($"Hello from {clientId}"));
await Task.Delay(TimeSpan.FromSeconds(15));
await client.SendAsync(Encoding.UTF8.GetBytes($"Goodbye from {clientId}"));
client.Close();
Log("UdpClient closed");
void Log(string message) =>
Console.WriteLine($"{DateTime.UtcNow:HH:mm:ss.fff}: {message}");