logo头像

技术引领生活!

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99

#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地址,导致组播失败。

1
2
3
4
5
6
7
8
9
10
11
12
13
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已经修复了这个问题
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    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;
    }
  2. 修改代码如下
    在工程文件中添加
    1
    2
    3
    win32 {
    LIBS += -lWs2_32
    }

修改关键代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
//添加头文件
#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 === 田文镜
支付宝打赏 微信打赏

您的支持是我前行的动力!