UDP Client网络重连后调用Send触发SocketException(10022)问题排查
UDP Client网络重连后场景2发送失败的问题分析与解决
问题描述
我在两种UDP发送场景中遇到了差异问题:
- 场景1:创建
UdpClient后,每次调用Send()时指定远程主机地址发送数据 - 场景2:创建
UdpClient后先调用Connect()设置默认远程主机,后续调用Send()无需指定远程主机(官方文档说明:Connect方法通过指定的endPoint参数建立默认远程主机,建立后每次调用Send无需再指定远程主机)
两种场景的UdpClient创建逻辑基本一致(仅场景2会调用Connect),创建代码如下:
private static UdpClient CreateUdpClient(IPAddress localNetworkAdapter, IPEndPoint addressForReadingData) { var client = new UdpClient(); client.Client.ExclusiveAddressUse = false; client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); client.Client.Blocking = false; client.Client.Ttl = 10; client.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 10); client.Client.Bind(new IPEndPoint(localNetworkAdapter, 0)); client.Connect(addressForReadingData); return client; }
网络正常时两种场景均工作正常,但断开网络后重新连接:
- 场景1可自动恢复正常发送
- 场景2每次调用
Send()都会抛出SocketException(10022 - 提供了无效参数),必须关闭并重建UdpClient才能恢复
测试代码可复现该行为(修改scenario变量切换场景):
scenario=false时:网络断开时控制台输出错误,重连后恢复输出“Sent”scenario=true时:网络重连后控制台仍持续输出错误
测试代码:
using System.Net; using System.Net.Sockets; namespace UdpTester { static class Program { static void Main(string[] args) { // scenario 1 -> false // scenario 2 -> true bool scenario = true; IPEndPoint remoteHost = new IPEndPoint(IPAddress.Parse("172.30.65.8"), 3000); IPAddress localNetworkAdapter = IPAddress.Parse("172.30.65.3"); ushort udpMaxPacketSizeInBytes = 1400; Console.WriteLine("Starting UDP tester:"); Console.WriteLine($"Remote host: {remoteHost}"); Console.WriteLine($"Network adapter: {localNetworkAdapter}"); Console.WriteLine($"Max packet size (Bytes): {udpMaxPacketSizeInBytes}"); var udpClient = CreateClient(localNetworkAdapter, remoteHost, udpMaxPacketSizeInBytes, scenario); SendData(udpClient, remoteHost, scenario); } static UdpClient CreateClient(IPAddress localNetworkAdapter, IPEndPoint remoteHost, int bufferSize, bool scenario) { var client = new UdpClient(); client.Client.ExclusiveAddressUse = false; client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); client.Client.SendBufferSize = bufferSize; client.Client.Blocking = false; client.Client.Ttl = 10; client.Client.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, 10); client.Client.Bind(new IPEndPoint(localNetworkAdapter, 0)); if(scenario) client.Connect(remoteHost); return client; } static void SendData(UdpClient client, IPEndPoint remoteHost, bool scenario) { Console.WriteLine("\nSending data..."); while (true) { try { if(scenario) client.Send(new byte[1], 1); else client.Send(new byte[1], 1, remoteHost); Thread.Sleep(500); Console.WriteLine("Sent"); } catch (Exception ex) { Console.WriteLine(ex); } } } } }
原因分析
- UDP是无连接协议,
UdpClient.Connect()并非建立真正的TCP式连接,而是在底层socket上绑定了固定的远程地址,同时会过滤接收的数据包(只接收来自该地址的包)。 - 当网络断开后,底层socket会记录一个错误状态(比如路由不可达)。场景1每次调用
Send(byte[], int, IPEndPoint)时,会重新解析目标地址的路由信息,相当于重置了socket的错误状态;而场景2调用无参数的Send()时,会复用之前绑定的远程地址和缓存的错误状态,此时即使网络恢复,socket仍认为目标地址不可用,从而抛出10022无效参数错误。 - 10022错误在这里的本质是:socket在错误状态下,使用绑定的默认地址发送数据时,底层无法找到有效的路由,导致参数校验失败。
解决建议
方案1:捕获10022错误后重建UdpClient
在异常捕获逻辑中,当检测到SocketException错误码为10022时,销毁旧的UdpClient并重新创建连接:
catch (SocketException ex) { if (ex.ErrorCode == 10022 && scenario) { client.Close(); client = CreateClient(localNetworkAdapter, remoteHost, udpMaxPacketSizeInBytes, scenario); Console.WriteLine("Reconnected UDP client"); } else { Console.WriteLine(ex); } }
注:直接调用client.Connect(remoteHost)可能无法彻底清除底层socket的错误状态,重建实例是更可靠的方式。
方案2:监听网络状态重置连接
通过NetworkChange.NetworkAvailabilityChanged事件监听网络状态变化,当网络恢复时自动重建UdpClient实例,避免手动处理异常逻辑。
方案3:统一使用指定地址的Send()
如果业务允许,放弃使用Connect()绑定默认地址的方式,统一采用场景1的发送逻辑,每次发送时指定远程主机地址,这样网络重连后无需额外处理即可自动恢复。
内容的提问来源于stack exchange,提问作者Whitehouse112
相关产品推荐
相关产品推荐

