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

相关推荐
ouynagda7 分钟前
Linux Socket 网络编程学习笔记
linux·网络·学习
牛马也想出海10 分钟前
海外社媒代理IP:TikTok、IG等6大社媒平台防封号IP选型指南
运维·服务器·网络·新媒体运营
昌原的儿子LEO16 分钟前
【Linux网络编程】TCP套接字核心函数详解|三次握手|send/recv/listen/accept接口梳理
linux·网络·tcp/ip
我爱cope16 分钟前
【计算机网络 | 网络层12:SDN:控制平面与数据平面分离意味着什么?】
网络·学习·计算机网络·平面
阿崽meitoufa19 分钟前
Prompt Engineering for Agent:让 Agent 稳定运行的提示词写法
网络·人工智能·microsoft
筝筝ba22 分钟前
extended/factory_reset.robot
linux·运维·服务器·网络·网络协议·p2p
网安小学生(兼顾数据库版)23 分钟前
CRA漏洞通报义务倒计时:9.11生效要求深度解读
网络·安全·web安全
优化Henry27 分钟前
5G网络切片之S-NSSAIlist相关参数解析
运维·服务器·网络·笔记·学习·信息与通信
某林2121 小时前
机器人收不住、转不动?执行器死区的原理与三层补偿设计
前端·网络·c++·架构·机器人
多多鼠1 小时前
Tool Calling的信任边界:从协议校验到执行沙箱的完整链路设计
运维·开发语言·网络·人工智能·python·langchain