Qt低版本多网卡组播bug

原文地址

最近在某个项目中,发现了一个低版本Qt的bug,导致组播无法正常使用,经过一番排查,终于找到了原因,特此记录。

环境

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

现象

在Qt5.7.0版本中,使用组播发送数据时,发现数据无法接收,经过长时间的排查,发现是Qt的bug,具体现象如下:

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

排查步骤

  1. 使用调试工具

    • 使用地址 0.0.0.0: port 不能接收到数据
    • 使用地址 192.168.1.100: port 可以接收到数据
    • 使用地址 239.255.255.255: port 不能接收到数据
  2. 测试自带的组播收发例子

    • 本机测试可以正常接收数据
    • 两台电脑测试不能接收数据

尝试解决

经过一顿搜索,加上长时间的摸索(本机的虚拟网卡太多),长时间折腾后发现只有一个网卡的时候可以正常。必须祭出终极大杀器 socket sdk 如果还不行都不知道该怎么办了,结果测试竟然可行

c++ 复制代码
#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地址,导致组播失败。

c++ 复制代码
        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;
        }

解决方案

  1. 更新Qt版本,最新版的Qt已经修复了这个问题
c++ 复制代码
    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;
        }
  1. 修改代码如下
    在工程文件中添加
qmake 复制代码
win32 {
    LIBS += -lWs2_32
}

修改关键代码

c++ 复制代码
//添加头文件
#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

血的经验

  1. 使用三方标准工具测试
  2. 使用原始sdk测试
  3. Qt也可能存在bug
  4. 搜索引擎可能存在误导
  5. csdn === 田文镜
相关推荐
爱思考的小伙15 小时前
Qt-02:信号与槽
开发语言·qt
森G16 小时前
22、GUI控件类---------常见界面组件类
qt
森G18 小时前
21、信号和槽详解---------QT基础
qt
西装没钱买18 小时前
QT组播的建立和使用(绑定特定的网卡,绑定特定IP)
网络·c++·qt·udp·udp组播
森G19 小时前
20、元对象系统---------QT基础
qt
Laurence19 小时前
CMake 报错 Failed to find required Qt component WebEngineWidgets
qt·webengine·cmake·找不到
习惯就好zz19 小时前
Qt Quick 系统托盘完整实践
开发语言·qt·qml·系统托盘·system tray·qapplication·qguiapplication
笨笨马甲19 小时前
Qt集成OpenCV
开发语言·qt
笨笨马甲19 小时前
Qt 工业机器视觉开发
开发语言·qt
小灰灰搞电子20 小时前
Qt 打印输出:printf与qDebug的区别
开发语言·qt