You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何使用UdpClient发送后会导致后续接收操作失败?

UDP聊天服务器发送到已关闭客户端导致ReceiveAsync抛出异常问题

我正在实现一个UDP聊天服务器,功能是向所有曾经发送过消息的客户端转发消息——所有发过消息的客户端都会收到其他客户端的消息。所有操作都通过UdpClient在独立进程中完成(所有网络连接都在同一系统内,所以我认为UDP的不可靠性不会造成影响)。

服务器核心代码大致如下:

var udpClient = new UdpClient(9001);
while (true)
{
    var packet = await udpClient.ReceiveAsync();
    // 在此处向之前发送过消息的客户端转发消息
}

客户端简化代码:

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(Memory1 buffer, SocketFlags socketFlags, EndPoint remoteEndPoint, CancellationToken cancellationToken) at System.Net.Sockets.Socket.ReceiveFromAsync(ArraySegment1 buffer, SocketFlags socketFlags, EndPoint remoteEndPoint)
at System.Net.Sockets.UdpClient.ReceiveAsync()
at Program.

$(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}");

内容的提问来源于stack exchange,提问作者Jon Skeet

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.13 13:35:40