当前位置: 首页 > article >正文

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 如果还不行都不知道该怎么办了,结果测试竟然可行


#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;
        }

解决方案

  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;
        }
  1. 修改代码如下
    在工程文件中添加
win32 {
    LIBS += -lWs2_32
}

修改关键代码

//添加头文件
#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 === 田文镜

http://www.kler.cn/a/384068.html

相关文章:

  • 一个百度、必应搜索引擎图片获取下载的工具包
  • API网关如何在iPaaS平台中助企业构建安全高效的API生态体系
  • Python OpenCV 傅里叶变换
  • 关于诊断中的各种时间参数
  • 做遥感算法?GIS开发?新型测绘?哪个专业更注重编程能力?
  • 通俗易懂讲STM32为GPIO的8种模式(上拉输入、下拉输入、模拟输入、浮空输入,开漏输出,推挽输出)
  • 智能获客SCRM提升客户管理效率的全新解决方案
  • 如何在 uniapp 中实现图形验证码
  • m6ATM
  • 一文解秘Rust如何与Java互操作
  • ArkTS中的组件基础、状态管理、样式处理、class语法以及界面渲染
  • 实战攻略 | ClickHouse优化之FINAL查询加速
  • 雷军-2022.8小米创业思考-11-新零售:用电商思维做新零售,极致的效率+极致的体验。也有弯路,重回极致效率的轨道上。
  • 数据库期末考试简答题
  • 元器件篇——自恢复保险丝(PPTC)
  • CNN和RCNN的关系和区别
  • Java基础Day-Thirteen
  • 【算法——二维动态规划】
  • 探索前端框架:为你的项目选择合适的UI工具箱
  • React native Text Webview 处理字体大小的变化
  • Docker + Jenkins + gitee 实现CICD环境搭建
  • 部署stable-diffusion3.5 大模型,文生图
  • 【计算机视觉基础】卷积
  • 智慧场馆:安全、节能与智能化管理的未来
  • 西门子S7-1200 PLC脉冲控制实例的完整流程
  • 软件著作权申请教程(超详细)(2024新版)软著申请