工控机之UDP组播

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 → 管理平台正常收到

相关推荐
微硬创新1 小时前
耐达讯自动化:16路4-20mA模拟信号,该怎样平稳接入PROFINET控制系统?
人工智能·物联网·网络协议·自动化·信息与通信
小疆智控2 小时前
工业跨网通讯实践EtherCAT转TCPIP网关关键技术
网络·网络协议
和煦的糖果2 小时前
项目1: TurtleBot3 自主导航的第一阶段学习
网络
fhqlongteng2 小时前
TCP服务器断开客户端的方法
服务器·网络·tcp/ip
啦啦啦啦啦zzzz2 小时前
5种IO模型
运维·服务器·网络·c++
打妖妖灵滴哪吒3 小时前
OSI七层网络参考模型(图文版)
网络
CodeJourney_J3 小时前
网络安全简述-加密技术
网络·安全·密码学
xiaoxiangsiyan4 小时前
企业网络运维自动化与管理协议深度指南
linux·运维·服务器·网络·学习·自动化
乱七八糟的屋子4 小时前
libnice 超完整使用教程|C语言ICE/P2P公网穿透入门到高阶实战
网络协议·内网穿透·p2p·libnice·ice协议·stun/turn·webrtc底层