向所有可用的网络卡广播UDP消息

15

我需要发送一个UDP消息到特定的IP和端口。

由于有三个网络卡,

10.1.x.x
10.2.x.x
10.4.x.x

我发送UDP消息时,只能在一个网络适配器中接收到消息...其他IP地址都无法接收。

我想在发送消息时检查网络适配器。我该如何做?


目前我正在使用以下方法:

IPEndPoint localEndPoint = new IPEndPoint(IPAddress.Parse(LocalIP), 0);
IPEndPoint targetEndPoint = new IPEndPoint(TargetIP, iTargetPort);
UdpClient sendUdpClient = new UdpClient(localEndPoint);
int numBytesSent = sendUdpClient.Send(CombineHeaderBody, CombineHeaderBody.Length, targetEndPoint);

你能更明确地解释一下你想要做什么吗? - Simeon Pilgrim
我的应用程序将向10个以上系统中的一个应用程序发送消息。其中所有系统都位于三个不同的网络卡中。例如10.1.x.x / 10.2.x.x / 10.4.x.x。我只能在一个网络卡10.1.x.x中接收消息,而不能在其他两个网络卡中接收消息。因此,我想检查网络卡的可用性,然后再发送消息。谢谢。 - Anuya
所以这条消息只是用于检查网络可用性,还是有其他有效载荷/含义? - Simeon Pilgrim
如果您想要实现冗余,以应对发送端连接到网络A,但接收端由于网络卡/电缆故障而断开与A的连接,却连接到B和C的情况,并且希望您的消息能够到达接收端,那么像Rex所说的那样发送消息应该可以解决问题,但是您需要在接收端处理重复和可能的乱序。 - Simeon Pilgrim
如果您只是想测试所有网络中每个主机的连接性,则向A、B和C网络上每个主机的网络地址发送单播消息。 - Simeon Pilgrim
当我尝试使用网络适配器A时,它正常工作。当我尝试使用网络适配器B时,出现错误:尝试对无法访问的主机进行套接字操作。当我尝试使用网络适配器C时,出现错误:尝试对无法访问的主机进行套接字操作。 - Anuya
4个回答

18

这实际上比听起来要困难,因为如果您有多个接口,则广播消息不会始终发送到所有接口。为了解决这个问题,我创建了这个类。

public class MyUdpClient : UdpClient
{
   public MyUdpClient() : base()
   {
      //Calls the protected Client property belonging to the UdpClient base class.
      Socket s = this.Client;
      //Uses the Socket returned by Client to set an option that is not available using UdpClient.
      s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
      s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, 1);
   }

   public MyUdpClient(IPEndPoint ipLocalEndPoint) : base(ipLocalEndPoint)
   {
      //Calls the protected Client property belonging to the UdpClient base class.
      Socket s = this.Client;
      //Uses the Socket returned by Client to set an option that is not available using UdpClient.
      s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
      s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, 1);
   }

}

我使用类似下面的代码来通过广播发送UDP数据包。我使用IPAddress.BroadcastMyUdpClient,这与你的代码不同。

IPEndPoint  localEndPoint  = new IPEndPoint(IPAddress.Parse(LocalIP), 0);
IPEndPoint  targetEndPoint = new IPEndPoint(IPAddress.Broadcast, iTargetPort);
MyUdpClient sendUdpClient  = new MyUdpClient(localEndPoint);
int numBytesSent = sendUdpClient.Send(CombineHeaderBody, CombineHeaderBody.Length, targetEndPoint);
此外,您应该注意,当您使用特定的ipaddress而不是广播时,路由表仅将其发送到与地址匹配的接口。因此,在您的示例中,使用单播。您需要将LocalIP设置为要发送到的本地接口的IP。有三个接口,您需要选择正确的一个来使用。
IPEndPoint  localEndPoint  = new IPEndPoint(IPAddress.Parse(LocalIP), 0);
IPEndPoint  targetEndPoint = new IPEndPoint(TargetIP, iTargetPort);
MyUdpClient sendUdpClient  = new MyUdpClient(localEndPoint);
int numBytesSent = sendUdpClient.Send(CombineHeaderBody, CombineHeaderBody.Length, targetEndPoint);

因为路由被关闭,你可能会在所有接口上看到它,但对于单播情况,你需要进行测试。

如果你不关心发送的IP或端口,你可以使用以下代码。

IPEndPoint  targetEndPoint = new IPEndPoint(TargetIP, iTargetPort);
MyUdpClient sendUdpClient  = new MyUdpClient();
int numBytesSent = sendUdpClient.Send(CombineHeaderBody, CombineHeaderBody.Length, targetEndPoint);

或用于广播

IPEndPoint  targetEndPoint = new IPEndPoint(IPAddress.Broadcast, iTargetPort);
MyUdpClient sendUdpClient  = new MyUdpClient();
int numBytesSent = sendUdpClient.Send(CombineHeaderBody, CombineHeaderBody.Length, targetEndPoint);

IPAddress.Broadcast存在的问题是它们不能通过任何网关路由。为了解决这个问题,您可以创建一个IPAddresses列表,然后循环发送。另外,由于发送可能会因网络问题而失败,而您无法控制此类问题,因此还应该添加try/catch块。

ArrayList ip_addr_acq = new ArrayList();

ip_addr_acq.Add(IPAddress.Parse("10.1.1.1")); // add to list of address to send to

try
{
   foreach (IPAddress curAdd in ip_addr_acq) 
   {
       IPEndPoint  targetEndPoint = new IPEndPoint(curAdd , iTargetPort);
       MyUdpClient sendUdpClient  = new MyUdpClient();
       int numBytesSent = sendUdpClient.Send(CombineHeaderBody, CombineHeaderBody.Length, targetEndPoint);

       Thread.Sleep(40); //small delay between each message
    }
 }
 catch
 {
 // handle any exceptions
 }

编辑:请参见上面更改为使用多个接口的单播,以及尝试向可用网络单播数据包时出现问题


你好Rex Logan,当我尝试使用你的代码时,我遇到了以下错误: 尝试对不可达主机进行套接字操作。我感到困惑,因为拥有该IP的网络适配器确实存在。 当我调试以下行… int numBytesSent = sendUdpClient.Send(CombineHeaderBody, CombineHeaderBody.Length, targetEndPoint);我看到targetEndPoint = {10.4.1.2:1982}。当targetEndPoint是{10.1.1.1:1982}时,我在远程计算机上收到了数据包。:( - Anuya
如果你发送到 IPAddress.Broadcast 会发生什么?你也可以尝试我添加的简化版本。 - Rex Logan
你也可以尝试注释掉 s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, 1); 或者 s.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1) 看看会发生什么。 - Rex Logan
一样的。可以找到那个IP地址。 我尝试ping 10.4.1.2,但它显示“目标主机不可达”。所以现在是网络问题还是应用程序问题?谢谢。 - Anuya
当我执行ipaddress.broadcast时,远程机器上没有收到数据包。 - Anuya

4
我通过从每个适配器(使用bind)发送UDP广播来解决了这个问题:
public static void SNCT_SendBroadcast(out List<MyDevice> DevicesList)
{
DevicesList = new List<MyDevice>();
byte[] data = new byte[2]; //broadcast data
data[0] = 0x0A;
data[1] = 0x60;

IPEndPoint ip = new IPEndPoint(IPAddress.Broadcast, 45000); //braodcast IP address, and corresponding port

NetworkInterface[] nics = System.Net.NetworkInformation.NetworkInterface.GetAllNetworkInterfaces(); //get all network interfaces of the computer

foreach (NetworkInterface adapter in nics)
{
// Only select interfaces that are Ethernet type and support IPv4 (important to minimize waiting time)
if (adapter.NetworkInterfaceType != NetworkInterfaceType.Ethernet) { continue; }
if (adapter.Supports(NetworkInterfaceComponent.IPv4) == false) { continue; }
try
{
    IPInterfaceProperties adapterProperties = adapter.GetIPProperties();    
    foreach (var ua in adapterProperties.UnicastAddresses)
    {
        if (ua.Address.AddressFamily == System.Net.Sockets.AddressFamily.InterNetwork)
        {
         //SEND BROADCAST IN THE ADAPTER
            //1) Set the socket as UDP Client
            Socket bcSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); //broadcast socket
            //2) Set socker options
            bcSocket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
            bcSocket.ReceiveTimeout = 200; //receive timout 200ms
            //3) Bind to the current selected adapter
            IPEndPoint myLocalEndPoint = new IPEndPoint(ua.Address, 45000);
            bcSocket.Bind(myLocalEndPoint);
            //4) Send the broadcast data
            bcSocket.SendTo(data, ip);

        //RECEIVE BROADCAST IN THE ADAPTER
            int BUFFER_SIZE_ANSWER = 1024;
            byte[] bufferAnswer = new byte[BUFFER_SIZE_ANSWER];
            do
            {
                try
                {
                    bcSocket.Receive(bufferAnswer);
                    DevicesList.Add(GetMyDevice(bufferAnswer)); //Corresponding functions to get the devices information. Depends on the application.
                }
                catch { break; }

            } while (bcSocket.ReceiveTimeout != 0); //fixed receive timeout for each adapter that supports our broadcast
            bcSocket.Close();
        }
    }
  }
  catch { }
}
return;
}

稍加修改后,这对我的需求很好用,谢谢!1)您需要将外部Try/Catch块移动到ForEach UnicastAddr内,并添加一个finally块以处理bcSocket对象的释放,因为在某些情况下绑定会抛出异常。2)仅限于NetworkInterfaceType.Ethernet的限定条件将忽略一些常见硬件,如无线网卡等。(NetworkInterfaceType.Wireless80211) - bakerhillpins

4

Rex的回答的拓展。这使你不必硬编码你想要广播的IP地址。循环遍历所有接口,检查它们是否正常工作,确保它具有IPv4信息,并与之关联的是IPv4地址。只需将“data”变量更改为您想要广播的任何数据,将“target”端口更改为您想要的端口即可。小缺点是,如果一个接口有多个IP地址与其关联,它将从每个地址广播。注意:这也会尝试通过任何VPN适配器发送广播(通过网络和共享中心/网络连接,Win 7+验证),如果您想要接收响应,您将需要保存所有客户端。您也不需要第二个类。

    foreach( NetworkInterface ni in NetworkInterface.GetAllNetworkInterfaces() ) {
        if( ni.OperationalStatus == OperationalStatus.Up && ni.SupportsMulticast && ni.GetIPProperties().GetIPv4Properties() != null ) {
            int id = ni.GetIPProperties().GetIPv4Properties().Index;
            if( NetworkInterface.LoopbackInterfaceIndex != id ) {
                foreach(UnicastIPAddressInformation uip in ni.GetIPProperties().UnicastAddresses ) {
                    if( uip.Address.AddressFamily == AddressFamily.InterNetwork ) {
                        IPEndPoint local = new IPEndPoint(uip.Address.Address, 0);
                        UdpClient udpc = new UdpClient(local);
                        udpc.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast, 1);
                        udpc.Client.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.DontRoute, 1);
                        byte[] data = new byte[10]{1,2,3,4,5,6,7,8,9,10};
                        IPEndPoint target = new IPEndPoint(IPAddress.Broadcast, 48888);
                        udpc.Send(data,data.Length, target);
                    }
                }
            }
        }
    }

1

如果您发送到特定的IP地址,则是单播,而不是广播。


哦,没错。但是在发送之前我应该选择网络,对吧? - Anuya
1
你想通过单个网络卡将数据包发送到远程主机,以覆盖本地所有网络卡吗?如果你想测试每个网络卡,请先找到该网络上的一个主机并向其发送消息,让操作系统帮你路由。 - Simeon Pilgrim
但是操作系统总是路由到第一张网络卡10.1.x.x。 - Anuya
如果您想发送到不同的网卡,请将其发送到主机的不同地址。也就是说,如果它们都在三个网络上。这取决于您要实现什么目标,是测试网络连接性还是发送实际的更高层消息。 - Simeon Pilgrim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接