C#中使用同一端口收发UDP数据报的问题咨询
UDP同一端口收发问题及解决建议
问题描述
我使用端口2100同时收发UDP数据包,通过Wireshark确认:发送和接收的数据包都使用该端口,远程主机也返回了正确的响应数据,但EndReceive()函数只接收到我自己发送的原始数据报,收不到远程设备的回复。
我推测原因是:使用同一端口且监听IPAddress.Any,导致套接字接收到了自身发送的数据包。
我的需求是查询远程设备IP:发送包含设备序列号的广播查询包,对应设备会回复包含其IP的数据包。该远程设备仅响应来自端口2100的请求,且回复也发送到端口2100。
最初尝试client.Receive()函数,结果相同;切换为EndReceive()后问题仍未解决。想请教两个问题:
- 有什么解决当前接收问题的建议?
- 是否有办法确定PC发送数据报时使用的IP地址?
原始代码
static int numRcvdBytes; static Byte[] rcvdBytes = new Byte[512]; static void SendRequest() { numRcvdBytes = 0; var data = Encoding.ASCII.GetBytes("0C1>" + amp.serial + ";000,P \r\n"); UdpClient client = new UdpClient(); IPEndPoint lep = new IPEndPoint(IPAddress.Any, 2100); // 设置源端口为2100 IPEndPoint rep = new IPEndPoint(IPAddress.Parse(BroadcastIP), 2100); // 设置目标端口为2100 client.ExclusiveAddressUse = false; client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); client.Client.Bind(lep); client.BeginReceive(DataReceived, client); client.Send(data, data.Length, rep); Thread.Sleep(50); // 等待远程主机响应 if (numRcvdBytes > 2) { for ( int b=0; b < numRcvdBytes-2; b++) { Console.Write("{0}", Convert.ToChar(rcvdBytes[b]) ); } } else { LogEvent(tw, " Serial# " + amp.serial + " Not Found!" ); } } static void DataReceived(IAsyncResult ar) { UdpClient c = (UdpClient)ar.AsyncState; IPEndPoint receivedIpEndPoint = new IPEndPoint(IPAddress.Any, 0); Byte[] receivedBytes = c.EndReceive(ar, ref receivedIpEndPoint); Array.Copy(receivedBytes, 0, rcvdBytes, 0, receivedBytes.Length); numRcvdBytes = receivedBytes.Length; }
解决建议
1. 过滤自身发送的数据包
在DataReceived回调中,通过receivedIpEndPoint获取发送方的IP,判断是否为本机发送的数据包,若是则直接忽略,不处理。
2. 获取发送数据报的本机IP
发送数据包后,通过client.Client.LocalEndPoint可以获取系统实际使用的出口IP(因为绑定的是IPAddress.Any,系统会自动选择合适的网卡IP发送),代码如下:
IPAddress localSendIp = ((IPEndPoint)client.Client.LocalEndPoint).Address;
拿到这个IP后,在接收回调里对比收到的数据包来源IP,即可过滤掉自身发送的包。
3. 优化异步接收与资源管理
- 不要用固定
Thread.Sleep(50)等待响应,建议延长等待时间(比如1秒),或者用TaskCompletionSource实现异步等待,避免因为响应延迟导致接收不到数据。 - 使用
using语句包裹UdpClient,确保资源正确释放,同时避免在接收回调执行前提前释放客户端。
4. 补充Socket广播选项
发送广播包时,建议添加Broadcast选项,确保系统允许发送广播数据:
client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true);
修改后的代码示例
static int numRcvdBytes; static Byte[] rcvdBytes = new Byte[512]; static IPAddress _localSendIp; // 存储发送时使用的本机IP static void SendRequest() { numRcvdBytes = 0; var data = Encoding.ASCII.GetBytes("0C1>" + amp.serial + ";000,P \r\n"); // 使用using确保UdpClient资源正确释放 using (UdpClient client = new UdpClient()) { IPEndPoint lep = new IPEndPoint(IPAddress.Any, 2100); IPEndPoint rep = new IPEndPoint(IPAddress.Parse(BroadcastIP), 2100); client.ExclusiveAddressUse = false; client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress, true); client.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, true); client.Client.Bind(lep); // 先发送数据包,再获取实际使用的本地IP client.Send(data, data.Length, rep); _localSendIp = ((IPEndPoint)client.Client.LocalEndPoint).Address; // 启动异步接收 client.BeginReceive(DataReceived, client); // 延长等待时间,确保远程设备有足够时间响应 Thread.Sleep(1000); if (numRcvdBytes > 2) { for (int b = 0; b < numRcvdBytes - 2; b++) { Console.Write("{0}", Convert.ToChar(rcvdBytes[b])); } } else { LogEvent(tw, " Serial# " + amp.serial + " Not Found!"); } } } static void DataReceived(IAsyncResult ar) { UdpClient c = (UdpClient)ar.AsyncState; IPEndPoint receivedIpEndPoint = new IPEndPoint(IPAddress.Any, 0); Byte[] receivedBytes = c.EndReceive(ar, ref receivedIpEndPoint); // 过滤自身发送的数据包 if (receivedIpEndPoint.Address.Equals(_localSendIp)) { // 重新启动接收,等待下一个数据包 c.BeginReceive(DataReceived, c); return; } // 处理远程设备的响应 Array.Copy(receivedBytes, 0, rcvdBytes, 0, receivedBytes.Length); numRcvdBytes = receivedBytes.Length; }
内容的提问来源于stack exchange,提问作者Steve Trigero
相关产品推荐
相关产品推荐

