Qt低版本多网卡组播bug

news2024/11/13 0:08:06

原文地址

最近在某个项目中,发现了一个低版本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.coloradmin.cn/o/2238237.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

cv::intersectConvexConvex返回其中一个输入点集,两个点集不相交

问题&#xff1a;cv::intersectConvexConvex返回其中一个输入点集&#xff0c;但两个点集并不相交 版本&#xff1a;opencv 3.1.0 git上也有人反馈了intersectConvexConvex sometimes returning one of the input polygons in case of empty intersection #10044 是凸包嵌套判…

【刷题12】ctfshow刷题

来源&#xff1a;ctfshow easyPytHon_P 考点&#xff1a;代码审计&#xff0c;源代码查看 打开后查看源码&#xff0c;发现一个源码地址&#xff0c;打开看看 可以知道在此目录下有个flag.txt文件&#xff0c;再观察源码 from flask import request cmd: str request.form.get…

spark的学习-03

RDD的创建的两种方式&#xff1a; 方式一&#xff1a;并行化一个已存在的集合 方法&#xff1a;parallelize 并行的意思 将一个集合转换为RDD 方式二&#xff1a;读取外部共享存储系统 方法&#xff1a;textFile、wholeTextFile、newAPIHadoopRDD等 读取外部存储系统的数…

axios平替!用浏览器自带的fetch处理AJAX(兼容表单/JSON/文件上传)

fetch 是啥&#xff1f; fetch 函数是 JavaScript 中用于发送网络请求的内置 API&#xff0c;可以替代传统的 XMLHttpRequest。它可以发送 HTTP 请求&#xff08;如 GET、POST 等&#xff09;&#xff0c;并返回一个 Promise&#xff0c;从而简化异步操作 基本用法 /* 下面是…

Linux(CentOS)安装 Nginx

CentOS版本&#xff1a;CentOS 7 Nginx版本&#xff1a;1.24.0 有两种安装方式 一、通过 yum 安装 需要 root 权限&#xff0c;普通用户使用 sudo 进行命令操作 参考&#xff1a;https://nginx.org/en/linux_packages.html#RHEL 1、安装依赖 sudo yum install yum-utils 2…

[原创]手把手教学之前端0基础到就业——day11( Javascript )

文章目录 day11(Javascript)01Javascript①Javascript是什么②JavaScript组成③ Javascript的书写位置1. 行内式 (不推荐)2 . 内部位置使用 ( 内嵌式 )3. 外部位置使用 ( 外链式 ) 02变量1. 什么是变量2. 定义变量及赋值3. 注意事项4. 命名规范 03输入和输出1) 输出形式12) 输出…

【C++笔记】C++三大特性之继承

【C笔记】C三大特性之继承 &#x1f525;个人主页&#xff1a;大白的编程日记 &#x1f525;专栏&#xff1a;C笔记 文章目录 【C笔记】C三大特性之继承前言一.继承的概念及定义1.1 继承的概念1.2继承的定义1.3继承基类成员访问方式的变化1.4继承类模板 二.基类和派生类间的转…

Colorful/七彩虹iGame G-ONE Plus 12代处理器 Win11原厂OEM系统 带COLORFUL一键还原

安装完毕自带原厂驱动和预装软件以及一键恢复功能&#xff0c;自动重建COLORFUL RECOVERY功能&#xff0c;恢复到新机开箱状态。 【格式】&#xff1a;iso 【系统类型】&#xff1a;Windows11 原厂系统下载网址&#xff1a;http://www.bioxt.cn 注意&#xff1a;安装系统会…

【LeetCode】分发糖果 解题报告

135. 分发糖果 - 题目链接 n个孩子站成一排。给你一个整数数组ratings表示每个孩子的评分。 你需要按照以下要求&#xff0c;给这些孩子分发糖果&#xff1a; 每个孩子至少分配到1个糖果。相邻两个孩子评分更高的孩子会获得更多的糖果。 请你给每个孩子分发糖果&#xff0c;…

ArcGIS从Excel表格文件导入XY数据并定义坐标系与投影的方法

本文介绍在ArcMap软件中&#xff0c;从Excel表格文件中批量导入坐标点数据&#xff0c;将其保存为.shp矢量格式&#xff0c;并定义坐标系、转为投影坐标系的方法。 已知我们有一个Excel表格文件&#xff08;可以是.xls、.xlsx、.csv等多种不同的表格文件格式&#xff09;&#…

爬虫 - 二手交易电商平台数据采集 (一)

背景: 近期有一个需求需要采集某电商网站平台的商品数据进行分析。因此&#xff0c;我计划先用Python实现一个简单的版本&#xff0c;以快速测试技术的实现可能性&#xff0c;再用PHP实现一个更完整的版本。文章中涉及的技术仅为学习和测试用途&#xff0c;请勿用于商业或非法用…

「C/C++」C++标准库 之 #include<iostream> 标准输入输出

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

AI出图,在家装行业杀疯了!

家装行业作为一个庞大的产业&#xff0c;长期以来面临着诸多难题&#xff0c;而随着AIGC技术的蓬勃发展&#xff0c;AI促进家装设计行业迎来了新的春天。 在传统家装设计流程中&#xff0c;相信大家对“设计环节充满了繁琐与复杂”有着非常深刻的体验&#xff0c;设计师需要花…

MySQL核心业务大表归档过程

记录一下2年前的MySQL大表的归档&#xff0c;当时刚到公司&#xff0c;发现MySQL的业务核心库&#xff0c;超过亿条的有7张表&#xff0c;最大的表有9亿多条&#xff0c;有37张表超过5百万条&#xff0c;部分表行数如下&#xff1a; 在测试的MySQL环境 &#xff1a; pt-archiv…

深度学习——权重初始化、评估指标、梯度消失和梯度爆炸

文章目录 &#x1f33a;深度学习面试八股汇总&#x1f33a;权重初始化零初始化 (Zero Initialization)随机初始化 (Random Initialization)Xavier 初始化&#xff08;Glorot 初始化&#xff09;He 初始化正交初始化&#xff08;Orthogonal Initialization&#xff09;预训练模型…

Proteus中数码管动态扫描显示不全(已解决)

文章目录 前言解决方法后记 前言 我是直接把以前写的 51 数码管程序复制过来的&#xff0c;当时看的郭天祥的视频&#xff0c;先送段选&#xff0c;消隐后送位选&#xff0c;最后来个 1ms 的延时。 代码在 Proteus 中数码管静态是可以的&#xff0c;动态显示出了问题——显示…

简单说一下Pinia 和 Vuex的区别

Pinia 和 Vuex 是 Vue.js 生态系统中两种状态管理库&#xff0c;它们都用于管理复杂应用的状态。尽管它们的目标相似&#xff0c;但在设计和使用上有许多不同之处。以下是 Pinia 和 Vuex 的主要区别&#xff1a; 1. 设计理念 Vuex 集中式存储&#xff1a;Vuex 采用单一的集中…

个人博客静态样式部署

首页部分 views/layout/layoutwave.vue <script setup> import router from /router; import { ref, onMounted, onUnmounted } from vue import /assets/img/icons/font_p81061dps7l/iconfont.cssconst canvas ref(null) const canvasFa ref(null) const fish ref(n…

综合文化信息管理系统|基于java和小程序的综合文化信息管理系统设计与实现(源码+数据库+文档)

综合文化信息管理系统 目录 基于java和小程序的打印室预约系统设计与实现 一、前言 二、系统设计 三、系统功能设计 四、数据库设计 五、核心代码 六、论文参考 七、最新计算机毕设选题推荐 八、源码获取&#xff1a; 博主介绍&#xff1a;✌️大厂码农|毕设布道师&…

渗透测试(socket,namp,scapy)

socket:可以用来实现不同虚拟机或者不同计算机之间的通信。 socket常用函数&#xff1a; sock.bind(host,port) //host可接受client范围&#xff0c;以及连接的端口 sock.listen()//sever开启监听连接 sock.accpet()//返回 sock&#xff0c;addr 用来接受和发送数据 addr…