我正在尝试创建一个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 [...]
环境:
- Windows 11,x64
- .NET 6
这是预期的行为吗?我在服务器端使用 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();
服务器
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}");
unchecked
关键字并混合使用 uint/int 型变量?这里有什么问题吗:IOC_IN | IOC_VENDOR | 12U
? - user2357112