| 
UID:151927
注册时间2014-05-09最后登录2025-10-18在线时间1471小时
发帖109搜Ta的帖子精华0
金钱1201威望122贡献值2好评度112
访问TA的空间加好友用道具
     | 
 
最近在某个项目中,发现了一个低版本Qt(Qt5.7)的bug,导致组播无法正常使用,经过一番排查,终于找到了原因,特此记录。环境现象在Qt5.7.0版本中,使用组播发送数据时,发现数据无法接收,经过长时间的排查,发现是Qt的bug,具体现象如下: 1. 在Qt5.7.0版本中,使用组播发送数据时,发现数据无法接收。 2. 使用串口调试工具,发现发送的数据包没有问题(无论何种情况都可以)。 3. 使用wireshark抓包,发现发送的数据包没有问题。 4. 使用Qt自带的组播收发例子,本机测试发现可以正常接收数据, 但是当收发处于两台电脑时不能接收。排查步骤Qt:5.7.0 mingw32操作系统:windows 11
 尝试解决经过一顿搜索,加上长时间的摸索(本机的虚拟网卡太多),长时间折腾后发现只有一个网卡的时候可以正常。必须祭出终极大杀器 socket sdk 如果还不行都不知道该怎么办了,结果测试竟然可行使用调试工具使用地址 0.0.0.0: port 不能接收到数据使用地址 192.168.1.100: port 可以接收到数据使用地址 239.255.255.255: port 不能接收到数据
测试自带的组播收发例子
本机测试可以正常接收数据两台电脑测试不能接收数据
 #include <stdio.h>  #include <winsock2.h>  #include <ws2tcpip.h>  #pragma comment(lib, "ws2_32.lib")void sendData(SOCKET sock){    struct sockaddr_in dest_addr; // 目标地址结构体     // 设置目标地址     memset(&dest_addr, 0, sizeof(dest_addr));     dest_addr.sin_family = AF_INET; // IPv4     dest_addr.sin_port = htons(groupPort); // 目标端口号     dest_addr.sin_addr.s_addr = inet_addr(groupIp); // 目标IP地址    char *sendData = "hello world";    sendto(sock, sendData, strlen(sendData), 0, (const struct sockaddr *)&dest_addr, sizeof(dest_addr));}int main(int argc, char* argv[]){    unsigned short groupPort = 37080;    char *bindIp = "192.168.8.112";    char *localIp = "192.168.8.112";    char *groupIp = "239.255.255.250";    printf("%s\n%s\n%s\n%d\n", bindIp, localIp, groupIp, groupPort);    if(argc >= 5){        bindIp = argv[1];        localIp = argv[2];        groupIp = argv[3];        groupPort = atoi(argv[4]);    }    int iRet = 0;    WSADATA wsaData;    WSAStartup(MAKEWORD(2, 2), &wsaData);    SOCKET sock = socket(AF_INET, SOCK_DGRAM, 0);    sockaddr_in addr;    addr.sin_family = AF_INET;    addr.sin_addr.S_un.S_addr = inet_addr(bindIp);//INADDR_ANY;    //addr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");    addr.sin_port = htons(groupPort);    bool bOptval = true;    iRet = setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char*)&bOptval, sizeof(bOptval));    if (iRet != 0) {        printf("setsockopt fail:%d", WSAGetLastError());        return -1;    }    iRet = bind(sock, (sockaddr*)&addr, sizeof(addr));    if (iRet != 0) {        printf("bind fail:%d\n", WSAGetLastError());        return -1;    }    printf("socket:%d bind success\n", sock);    ip_mreq multiCast;    multiCast.imr_interface.S_un.S_addr = inet_addr(localIp);    multiCast.imr_multiaddr.S_un.S_addr = inet_addr(groupIp);    iRet = setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (char*)&multiCast, sizeof(multiCast));    if (iRet != 0) {        printf("setsockopt fail:%d\n", WSAGetLastError());        return -1;    }    printf("udp group start: %d, %d\n", IPPROTO_IP, IP_ADD_MEMBERSHIP);    int len = sizeof(sockaddr);    char strRecv[1024] = { 0 };    while (true)    {        memset(strRecv, 0, sizeof(strRecv));        iRet = recvfrom(sock, strRecv, sizeof(strRecv) - 1, 0, (sockaddr*)&addr, &len);        if (iRet <= 0) {            printf("recvfrom fail:%d", WSAGetLastError());            return -1;        }        printf("recv data:%s\n", strRecv);    }    closesocket(sock);    WSACleanup();    return 0;}
经过对比发现Qt的源码中地址 mreq4.imr_interface.s_addr 赋值时候 QHostAddress firstIP = addressEntries.first().ip();可能为IPV6地址,导致IPV6地址赋值给IPV4地址,导致组播失败。
 if (iface.isValid()) {            const QList<QNetworkAddressEntry> addressEntries = iface.addressEntries();            if (!addressEntries.isEmpty()) {                QHostAddress firstIP = addressEntries.first().ip();                mreq4.imr_interface.s_addr = htonl(firstIP.toIPv4Address());            } else {                d->setError(QAbstractSocket::NetworkError,                            QNativeSocketEnginePrivate::NetworkUnreachableErrorString);                return false;            }        } else {            mreq4.imr_interface.s_addr = INADDR_ANY;        }
解决方案
 [size=; font-size: 0.9em,0.9em]修改代码如下 在工程文件中添加[size=; font-size: 0.9em,0.9em]1.更新Qt版本,最新版的Qt已经修复了这个问题if (iface.isValid()) {            const QList<QNetworkAddressEntry> addressEntries = iface.addressEntries();            bool found = false;            for (const QNetworkAddressEntry &entry : addressEntries) {                const QHostAddress ip = entry.ip();                if (ip.protocol() == QAbstractSocket::IPv4Protocol) {                    mreq4.imr_interface.s_addr = htonl(ip.toIPv4Address());                    found = true;                    break;                }            }            if (!found) {                d->setError(QAbstractSocket::NetworkError,                            QNativeSocketEnginePrivate::NetworkUnreachableErrorString);                return false;            }        } else {            mreq4.imr_interface.s_addr = INADDR_ANY;        }
 //添加头文件#ifdef Q_OS_WIN32#include <winsock2.h>#include <ws2tcpip.h>#endif//...........................            //Qt 5.7 bug fix, 第一个IP可能为ip v6            if (firstIP.protocol() == groupAddress.protocol()) {                ok = udpsock->joinMulticastGroup(groupAddress, iface);            } else {#ifdef Q_OS_WIN32                for (int i = 0; i < addressEntries.size(); i++) {                    QHostAddress addrTemp = addressEntries.at(i).ip();                    if (addrTemp.protocol() == groupAddress.protocol()) {                        ip_mreq multiCast;                        multiCast.imr_interface.S_un.S_addr = inet_addr(                            addrTemp.toString().toUtf8().constData());                        multiCast.imr_multiaddr.S_un.S_addr = inet_addr(                            groupAddress.toString().toUtf8().constData());                        int res = setsockopt(udpsock->socketDescriptor(),                                             0,                                             12,                                             (char *) &multiCast,                                             sizeof(multiCast));                        ok = (res == 0);                        break;                    }                }#else                ok = udpsock->joinMulticastGroup(groupAddress, iface);#endif
 血的经验
 使用三方标准工具测试使用原始sdk测试Qt也可能存在bug搜索引擎可能存在误导csdn === 田文镜
 |