有多个网卡时组播收不到消息

遇到组播有概率收不到消息的问题,经分析是由于没有指定网络接口导致的。

该设备上有两个网卡,组播消息来自 192.168.32.31 所在的网络:

1> ipconfig
2
3Windows IP Configurtion
4
5Ethernet adapter 192.168.31.31
6
7        Connection-specific DNS Suffix  . :
8        IP Address. . . . . . . . . . . . : 192.168.31.31
9        Subnet Mask . . . . . . . . . . . : 255.255.255.0
10        Default Gateway . . . . . . . . . :
11
12Ethernet adapter 192.168.32.31
13
14        Connection-specific DNS Suffix  . :
15        IP Address. . . . . . . . . . . . : 192.168.32.31
16        Subnet Mask . . . . . . . . . . . : 255.255.255.0
17        Default Gateway . . . . . . . . . :

UDP Socket 绑定了 192.168.32.31 然后加入组播,实际上并不表示从 192.168.32.31 接收组播。

UDP 是无连接的,bind 函数只是 recv 时省略 IP 地址和端口号。

要从指定的网络接口接收组播,需要 bind 0.0.0.0 或组播地址,然后在加入组播时指定网络接口。

以 Linux 的 Socket API 为例:

1// 1. bind 时推荐使用 INADDR_ANY (0.0.0.0)
2struct sockaddr_in local_addr;
3memset(&local_addr, 0, sizeof(local_addr));
4local_addr.sin_family = AF_INET;
5local_addr.sin_addr.s_addr = htonl(INADDR_ANY); // 不要写 192.168.32.31,可以写组播地址
6local_addr.sin_port = htons(8888);              // 组播端口
7bind(sock, (struct sockaddr*)&local_addr, sizeof(local_addr));
8
9// 2. 在 setsockopt 中显式指定 192.168.32.31
10struct ip_mreqn mreqn;
11mreqn.imr_multiaddr.s_addr = inet_addr("229.0.0.5");     // 组播 IP
12mreqn.imr_interface.s_addr = inet_addr("192.168.32.31"); // 指定接收组播的网卡 IP
13mreqn.imr_ifindex = if_nametoindex("eth0");              // 指定接口 
14setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mreqn, sizeof(mreqn));
  • imr_interface 和 imr_ifindex 均可指定网络接口,只填一个即可,后者通常用于暂未分配地址或地址动态变化(如 DHCP)的接口。

以 Qt 的 QUdpSocket 为例,应当调用下面第二个函数手动指定网络接口:

1// 自动选择网络接口
2bool joinMulticastGroup(const QHostAddress &groupAddress);
3
4// 手动选择网络接口
5bool joinMulticastGroup(const QHostAddress &groupAddress, const QNetworkInterface &iface);