• 679阅读
  • 1回复

[原创]Qt低版本多网卡组播 [复制链接]

上一主题 下一主题
离线spygg
 

只看楼主 倒序阅读 楼主  发表于: 2024-11-10
最近在某个项目中,发现了一个低版本Qt(Qt5.7)的bug,导致组播无法正常使用,经过一番排查,终于找到了原因,特此记录。


环境

  • Qt:5.7.0 mingw32
  • 操作系统:windows 11


现象

在Qt5.7.0版本中,使用组播发送数据时,发现数据无法接收,经过长时间的排查,发现是Qt的bug,具体现象如下: 1. 在Qt5.7.0版本中,使用组播发送数据时,发现数据无法接收。 2. 使用串口调试工具,发现发送的数据包没有问题(无论何种情况都可以)。 3. 使用wireshark抓包,发现发送的数据包没有问题。 4. 使用Qt自带的组播收发例子,本机测试发现可以正常接收数据, 但是当收发处于两台电脑时不能接收。


排查步骤

  1. 使用调试工具
  2. 使用地址 0.0.0.0: port 不能接收到数据
  3. 使用地址 192.168.1.100: port 可以接收到数据
  4. 使用地址 239.255.255.255: port 不能接收到数据
  5. 测试自带的组播收发例子
  6. 本机测试可以正常接收数据
  7. 两台电脑测试不能接收数据


尝试解决

经过一顿搜索,加上长时间的摸索(本机的虚拟网卡太多),长时间折腾后发现只有一个网卡的时候可以正常。必须祭出终极大杀器 socket sdk 如果还不行都不知道该怎么办了,结果测试竟然可行
  1. #include <stdio.h>  
  2. #include <winsock2.h>  
  3. #include <ws2tcpip.h>  
  4. #pragma comment(lib, "ws2_32.lib")
  5. void sendData(SOCKET sock)
  6. {
  7.     struct sockaddr_in dest_addr; // 目标地址结构体
  8.      // 设置目标地址
  9.      memset(&dest_addr, 0, sizeof(dest_addr));
  10.      dest_addr.sin_family = AF_INET; // IPv4
  11.      dest_addr.sin_port = htons(groupPort); // 目标端口号
  12.      dest_addr.sin_addr.s_addr = inet_addr(groupIp); // 目标IP地址
  13.     char *sendData = "hello world";
  14.     sendto(sock, sendData, strlen(sendData), 0, (const struct sockaddr *)&dest_addr, sizeof(dest_addr));
  15. }
  16. int main(int argc, char* argv[])
  17. {
  18.     unsigned short groupPort = 37080;
  19.     char *bindIp = "192.168.8.112";
  20.     char *localIp = "192.168.8.112";
  21.     char *groupIp = "239.255.255.250";
  22.     printf("%s\n%s\n%s\n%d\n", bindIp, localIp, groupIp, groupPort);
  23.     if(argc >= 5){
  24.         bindIp = argv[1];
  25.         localIp = argv[2];
  26.         groupIp = argv[3];
  27.         groupPort = atoi(argv[4]);
  28.     }
  29.     int iRet = 0;
  30.     WSADATA wsaData;
  31.     WSAStartup(MAKEWORD(2, 2), &wsaData);
  32.     SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);
  33.     sockaddr_in addr;
  34.     addr.sin_family = AF_INET;
  35.     addr.sin_addr.S_un.S_addr = inet_addr(bindIp);//INADDR_ANY;
  36.     //addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
  37.     addr.sin_port = htons(groupPort);
  38.     bool bOptval = true;
  39.     iRet = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&bOptval, sizeof(bOptval));
  40.     if (iRet != 0) {
  41.         printf("setsockopt fail:%d", WSAGetLastError());
  42.         return -1;
  43.     }
  44.     iRet = bind(sock, (sockaddr*)&addr, sizeof(addr));
  45.     if (iRet != 0) {
  46.         printf("bind fail:%d\n", WSAGetLastError());
  47.         return -1;
  48.     }
  49.     printf("socket:%d bind success\n", sock);
  50.     ip_mreq multiCast;
  51.     multiCast.imr_interface.S_un.S_addr = inet_addr(localIp);
  52.     multiCast.imr_multiaddr.S_un.S_addr = inet_addr(groupIp);
  53.     iRet = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&multiCast, sizeof(multiCast));
  54.     if (iRet != 0) {
  55.         printf("setsockopt fail:%d\n", WSAGetLastError());
  56.         return -1;
  57.     }
  58.     printf("udp group start: %d, %d\n", IPPROTO_IP, IP_ADD_MEMBERSHIP);
  59.     int len = sizeof(sockaddr);
  60.     char strRecv[1024] = { 0 };
  61.     while (true)
  62.     {
  63.         memset(strRecv, 0, sizeof(strRecv));
  64.         iRet = recvfrom(sock, strRecv, sizeof(strRecv) - 1, 0, (sockaddr*)&addr, &len);
  65.         if (iRet <= 0) {
  66.             printf("recvfrom fail:%d", WSAGetLastError());
  67.             return -1;
  68.         }
  69.         printf("recv data:%s\n", strRecv);
  70.     }
  71.     closesocket(sock);
  72.     WSACleanup();
  73.     return 0;
  74. }

经过对比发现Qt的源码中地址 mreq4.imr_interface.s_addr 赋值时候 QHostAddress firstIP = addressEntries.first().ip();可能为IPV6地址,导致IPV6地址赋值给IPV4地址,导致组播失败。

  1. if (iface.isValid()) {
  2.             const QList<QNetworkAddressEntry> addressEntries = iface.addressEntries();
  3.             if (!addressEntries.isEmpty()) {
  4.                 QHostAddress firstIP = addressEntries.first().ip();
  5.                 mreq4.imr_interface.s_addr = htonl(firstIP.toIPv4Address());
  6.             } else {
  7.                 d->setError(QAbstractSocket::NetworkError,
  8.                             QNativeSocketEnginePrivate::NetworkUnreachableErrorString);
  9.                 return false;
  10.             }
  11.         } else {
  12.             mreq4.imr_interface.s_addr = INADDR_ANY;
  13.         }




解决方案

  1. 1.更新Qt版本,最新版的Qt已经修复了这个问题
  2. if (iface.isValid()) {
  3.             const QList<QNetworkAddressEntry> addressEntries = iface.addressEntries();
  4.             bool found = false;
  5.             for (const QNetworkAddressEntry &entry : addressEntries) {
  6.                 const QHostAddress ip = entry.ip();
  7.                 if (ip.protocol() == QAbstractSocket::IPv4Protocol) {
  8.                     mreq4.imr_interface.s_addr = htonl(ip.toIPv4Address());
  9.                     found = true;
  10.                     break;
  11.                 }
  12.             }
  13.             if (!found) {
  14.                 d->setError(QAbstractSocket::NetworkError,
  15.                             QNativeSocketEnginePrivate::NetworkUnreachableErrorString);
  16.                 return false;
  17.             }
  18.         } else {
  19.             mreq4.imr_interface.s_addr = INADDR_ANY;
  20.         }


[size=; font-size: 0.9em,0.9em]修改代码如下 在工程文件中添加
[size=; font-size: 0.9em,0.9em]
  1. //添加头文件
  2. #ifdef Q_OS_WIN32
  3. #include <winsock2.h>
  4. #include <ws2tcpip.h>
  5. #endif
  6. //...........................
  7.             //Qt 5.7 bug fix, 第一个IP可能为ip v6
  8.             if (firstIP.protocol() == groupAddress.protocol()) {
  9.                 ok = udpsock->joinMulticastGroup(groupAddress, iface);
  10.             } else {
  11. #ifdef Q_OS_WIN32
  12.                 for (int i = 0; i < addressEntries.size(); i++) {
  13.                     QHostAddress addrTemp = addressEntries.at(i).ip();
  14.                     if (addrTemp.protocol() == groupAddress.protocol()) {
  15.                         ip_mreq multiCast;
  16.                         multiCast.imr_interface.S_un.S_addr = inet_addr(
  17.                             addrTemp.toString().toUtf8().constData());
  18.                         multiCast.imr_multiaddr.S_un.S_addr = inet_addr(
  19.                             groupAddress.toString().toUtf8().constData());
  20.                         int res = setsockopt(udpsock->socketDescriptor(),
  21.                                              0,
  22.                                              12,
  23.                                              (char *) &multiCast,
  24.                                              sizeof(multiCast));
  25.                         ok = (res == 0);
  26.                         break;
  27.                     }
  28.                 }
  29. #else
  30.                 ok = udpsock->joinMulticastGroup(groupAddress, iface);
  31. #endif





血的经验

  1. 使用三方标准工具测试
  2. 使用原始sdk测试
  3. Qt也可能存在bug
  4. 搜索引擎可能存在误导
  5. csdn === 田文镜
签名就是这么浪
在线tanyue.esec

只看该作者 1楼 发表于: 2024-11-10
  
快速回复
限100 字节
 
上一个 下一个