cpp
// 1. 主线程创建对象
p_monitor_udpsocket = new MonitorUdpSocket;
// 2. 注释掉直接调用(正确,因为此时还在主线程)
//p_monitor_udpsocket->slot_initialize_udpsocket();
// 3. 移到子线程
p_monitor_udpsocket->moveToThread(&thread_monitor_udpsocket);
// 4. 启动线程事件循环
thread_monitor_udpsocket.start();
// 5. 建立信号槽连接
connect(this, &MainWindow::sig_init_udpmonitor,
p_monitor_udpsocket, &MonitorUdpSocket::slot_initialize_udpsocket);
// 6. 发射信号,slot_initialize_udpsocket 在子线程中执行
emit sig_init_udpmonitor();
这段代码的结构是正确的! 因为:
先 moveToThread ✓
再 start() 线程 ✓
通过信号槽触发 slot_initialize_udpsocket,在子线程执行 ✓
注释说明你理解了 QUdpSocket 不能跨线程调用 ✓
唯一需要确认的是:slot_initialize_udpsocket() 里是否在子线程中创建了 QUdpSocket。
void MonitorUdpSocket::slot_initialize_udpsocket() {
m_pSocket = new QUdpSocket(this); // 在子线程中创建
// ... bind 等操作
}
cpp
void MonitorUdpSocket::slot_initialize_udpsocket()
{
QString strLocalIp = Settings::getInstance()->value(KEY_MONITOR_SOCKETSET_LOCALIP).toString();
quint16 uLocalPort = Settings::getInstance()->value(KEY_MONITOR_SOCKETSET_LOCALPORT).toUInt();
QString strMulticastIp = Settings::getInstance()->value(KEY_MONITOR_SOCKETSET_MONITOR_MUTICAST_IP).toString();
const int MAX_RETRY = 5;
const int RETRY_INTERVAL_MS = 2000;
for (int retry = 0; retry < MAX_RETRY; ++retry)
{
// 1. 检查网卡
QNetworkInterface netIf = GetTargetIpInterface(strLocalIp);
if (!netIf.isValid())
{
Log::getInstance()->insert(Log::Usr, Log::Error,
QString("本地IP %1 对应的网卡不存在,第 %2 次重试")
.arg(strLocalIp).arg(retry + 1));
QThread::msleep(RETRY_INTERVAL_MS);
continue;
}
// 2. 创建 socket
mudpsocket = new QUdpSocket(this); // ← 指定 parent,自动管理生命周期
// 3. 指定组播出口网卡
mudpsocket->setMulticastInterface(netIf);
// 4. bind
bool bBindOk = mudpsocket->bind(QHostAddress(strLocalIp), uLocalPort,
QUdpSocket::DontShareAddress);
if (!bBindOk)
{
Log::getInstance()->insert(Log::Usr, Log::Error,
QString("bind %1:%2 失败: %3,第 %4 次重试")
.arg(strLocalIp).arg(uLocalPort)
.arg(mudpsocket->errorString()).arg(retry + 1));
delete mudpsocket;
mudpsocket = nullptr;
QThread::msleep(RETRY_INTERVAL_MS);
continue;
}
qDebug() << QString("监控网Udp接收对象成功绑定 %1:%2").arg(strLocalIp).arg(uLocalPort);
Log::getInstance()->insert(Log::Usr, Log::Info, "监控网Udp接收对象成功绑定本地IP");
// 5. bind 成功后才加入组播
bool bJoinOk = mudpsocket->joinMulticastGroup(QHostAddress(strMulticastIp));
if (!bJoinOk)
{
Log::getInstance()->insert(Log::Usr, Log::Error,
QString("加入组播 %1 失败: %2,第 %3 次重试")
.arg(strMulticastIp)
.arg(mudpsocket->errorString()).arg(retry + 1));
delete mudpsocket;
mudpsocket = nullptr;
QThread::msleep(RETRY_INTERVAL_MS);
continue;
}
// 6. 全部成功
connect(mudpsocket, &QUdpSocket::readyRead,
this, &MonitorUdpSocket::slot_RecvMonitorData);
QString strLog = QString("监控网Udp成功加入组播: %1").arg(strMulticastIp);
qDebug() << strLog;
Log::getInstance()->insert(Log::Usr, Log::Info, strLog);
break; // 成功,退出循环
}
if (!mudpsocket)
{
Log::getInstance()->insert(Log::Usr, Log::Error,
QString("监控网Udp初始化失败,已重试 %1 次").arg(MAX_RETRY));
}
// 移到循环外面,只连接一次
connect(this, &MonitorUdpSocket::sig_test,
this, &MonitorUdpSocket::slot_send_monitor_data);
}
cpp
void MonitorUdpSocket::slot_RecvMonitorData()
{
QByteArray badata;
while(mudpsocket->hasPendingDatagrams())
{
badata.resize(mudpsocket->pendingDatagramSize());
mudpsocket->readDatagram(badata.data(), badata.size());
qDebug()<<"From Mointor: "<<badata.toHex();
emit sig_receive_monitor_data(badata);
}
}
cpp
// 发送组播
void sendMulticast() {
QUdpSocket socket;
QByteArray data = "Hello Multicast Group";
QHostAddress groupAddr("224.0.0.100"); // 组播地址
socket.writeDatagram(data, groupAddr, 9999);
}
// 接收组播 - 需要加入组播组
void receiveMulticast() {
QUdpSocket socket;
socket.bind(QHostAddress::AnyIPv4, 9999, QUdpSocket::ShareAddress);
QHostAddress groupAddr("224.0.0.100");
// 加入组播组,指定接收的网络接口
socket.joinMulticastGroup(groupAddr);
connect(&socket, &QUdpSocket::readyRead, [&]() {
while (socket.hasPendingDatagrams()) {
QByteArray data;
data.resize(socket.pendingDatagramSize());
socket.readDatagram(data.data(), data.size());
qDebug() << "收到组播数据:" << data;
}
});
}
// 离开组播组
void leaveMulticast() {
socket.leaveMulticastGroup(QHostAddress("224.0.0.100"));
}
为什么要指定组播出口网卡?
一句话:多网卡机器上,不指定的话,组播数据可能走错网卡,对面根本收不到。
场景还原
工控机/服务器通常不止一块网卡:
┌──────────────────────────────────────┐
│ 你的工控机 │
│ │
│ 网卡1: 129.26.20.231 ───→ 监控网 │
│ 网卡2: 192.168.1.150 ──→ 采集网 │
│ 网卡3: 10.0.0.1 ──→ 管理网 │
│ │
└──────────────────────────────────────┘
你的配置:
monitor_socket_set/loclalIP=129.26.20.231 # 监控网专用IP
monitor_socket_set/monitor_muticast_IP=225.0.0.100 # 组播地址
monitor_socket_set/monitorPort=6666
// 不指定网卡的情况:
mudpsocket->bind(QHostAddress("129.26.20.231"), 6666);
// bind 只绑定了"收"的地址
mudpsocket->joinMulticastGroup(QHostAddress("225.0.0.100"));
// joinMulticastGroup 加入了组播组,但没指定用哪个网卡收
mudpsocket->writeDatagram(data, QHostAddress("225.0.0.100"), 6666);
// 发送时,操作系统查路由表,可能走默认路由 → 网卡2!
你期望:监控网卡1 → 225.0.0.100 → 监控网上的设备收到
实际: 采集网卡2 → 225.0.0.100 → 监控网设备 收不到!
指定网卡的作用
QNetworkInterface MonitorNetIf = GetTargetIpInterface("129.26.20.231");
mudpsocket->setMulticastInterface(MonitorNetIf);
这行代码告诉操作系统:"不管路由表怎么说,组播数据只从 129.26.20.231 这块网卡出去!"
两个层面都需要指定:
表格
操作 不指定 指定后
发送组播 走默认路由,可能走错网卡 强制从指定网卡发出
接收组播 joinMulticastGroup 后可能向错误网卡发 IGMP 报告 向正确网卡发 IGMP,路由器把组播流转发到正确网卡
实际案例
┌──────────────────────────────────────────────────┐
│ │
│ 监控网(129.26.20.x) 采集网(192.168.1.x) │
│ │ │ │
│ 交换机A 交换机B │
│ │ │ │
│ ┌───┴────┐ ┌───┴────┐ │
│ │管理平台 │ │采集设备 │ │
│ │.20.100 │ │.1.100 │ │
│ └────────┘ └────────┘ │
│ │
└──────────────────────────────────────────────────┘
你的机器:
网卡1: 129.26.20.231(监控网)
网卡2: 192.168.1.150(采集网)
不指定网卡 → 组播可能走 192.168.1.150 → 管理平台收不到
指定网卡 → 组播强制走 129.26.20.231 → 管理平台正常收到