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 === 田文镜
相关推荐
小短腿的代码世界10 小时前
Qt Bluetooth源码深度解析:从HCI协议到跨平台API的完整架构
开发语言·qt·架构
小短腿的代码世界11 小时前
Qt WebEngine多进程架构与IPC通信:从Chromium多进程到Qt信号槽融合
qt·架构·系统架构
尘中远12 小时前
【Qwt 7.0 系列】快速入门与核心新特性概览 —— 更现代的 Qt 数据可视化库
qt·数据可视化·绘图·qwt·科学绘图
初阳78514 小时前
【Qt】系统相关(3)——多线程
qt
初阳78517 小时前
【Qt】系统相关(2)——文件
开发语言·qt
尘中远1 天前
【Qwt 7.0 系列】坐标轴与刻度系统 —— 刻度引擎、网格、图例与刻度朝内
qt·数据可视化·qcustomplot·qwt·工业软件·科学绘图
sycmancia1 天前
Qt——多线程间的互斥
开发语言·qt
尘中远2 天前
【Qwt 7.0 系列】常用图表类型实战 —— 柱状图、散点图、箱线图与直方图
qt·qwt·工业软件·科学绘图
尘中远2 天前
【Qwt 7.0 系列】交互功能详解 —— 平移、缩放、坐标轴交互与数据拾取
qt·数据可视化·绘图·qcustomplot·qwt·科学绘图
sycmancia2 天前
Qt——进程与线程的概念
qt