我正在尝试创建一个 UDP 服务器,它可以向所有向其发送消息的客户端发送消息。真实情况要复杂一些,但最简单的方法是将其想象为一个聊天服务器:之前发送过消息的每个人都会收到其他客户端发送的所有消息。
所有这一切都是通过UdpClient
,在单独的过程中。 (虽然所有网络连接都在同一个系统内,所以我不thinkUDP 的不可靠性是这里的一个问题。)
服务器代码是这样的循环(稍后提供完整代码):
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
, that因异常而失败,我还没有找到恢复的方法。
如果我从不向断开连接的客户端发送消息,我就永远不会发现问题。基于此,我还创建了一个“立即失败”重现,它仅发送到假设未监听的端点,然后尝试接收。这会失败并出现相同的异常。
例外:
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
关闭整个服务器。在一个控制台中运行服务器,在另一个控制台中运行客户端。一旦客户端完成一次,再次运行它(以便它尝试发送到现已失效的原始客户端)。
默认 .NET 6 控制台应用程序项目模板适用于所有项目。
重现代码
首先是最简单的示例 - 但如果您想运行服务器和客户端,它们会在后面显示。
故意破坏服务器(立即失败)
基于确实是发送导致问题的假设,只需几行即可轻松重现:
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();
Server
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}");
Client
(我之前有一个循环实际上接收服务器发送的数据包,但这似乎没有任何区别,因此为了简单起见,我将其删除。)
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}");