news 2026/8/19 16:37:06

Qt实现UDP单播、组播和广播功能

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Qt实现UDP单播、组播和广播功能

一、UDP单播、广播和组播的说明

UDP是不可靠、无连接的,所以划分为发送方和接收方更好理解

1、单播

UDP是无连接的,进行单播通信时,必须要绑定接收方端口,发送方直接通过接收方的ip和绑定的端口进行通信。发送方可以绑定端口也可以不用绑定端口,不绑定端口的话,系统会随机分配端口。

对于多网卡来说,需要指定网卡,绑定一个网卡的ip。如果不进行显式的绑定操作,QUdpSocket对象将会使用默认的绑定方式,自动选择一个可用的 ip 地址进行绑定。

2、广播

对于只有1个网卡的主机来说,可以不用显示绑定ip,发送方直接发送广播就行,接收方绑定广播端口就行,这样才能看到收到的消息。

对于多网卡来说,要指定唯一的网卡ip并且在广播前要绑定广播端口。ip可以不用绑定,这样系统会随机分配一个网卡ip

3、组播

对于只有1个网卡的主机来说,可以不用绑定ip,直接绑定端口后加入组播就行。系统分配任意一个ip ,相当于QHostAddress::AnyIPv4。

对于多网卡来说,要指定唯一的网卡并且在加入组播前要绑定组播端口,ip可以不用绑定,系统分配任意一个ip

注意:指定网卡不等于指定ip!!!

1、使用setMulticastInterface方法可以指定一个明确的网卡,但并不意味着只有一个 IP 地址。一个网卡可以绑定多个 IP 地址,例如在同一台主机上同时存在有线网卡和无线网卡,它们可能都连接到同一局域网,并分别配置了不同的 IP 地址。此时,通过setMulticastInterface方法指定了一个明确的网卡后,并不确定使用哪个 IP 地址来进行组播通信。

如果需要确保使用特定的 IP 地址进行组播通信,则需要使用bind方法来将QUdpSocket对象绑定到具体的 IP 地址和端口上,这样每次进行组播通信时,都会使用该 IP 地址来发送和接收数据报文。

2、使用QHostAddress::AnyIPv4参数可以将QUdpSocket对象绑定到本机的所有 IPv4 地址。这意味着,该QUdpSocket对象可以接收通过本机的任意一个 IPv4 地址发送到指定端口的数据包。

然而,需要注意的是,绑定到多个 IPv4 地址并不意味着可以同时从多个地址接收数据包。在任何给定的时刻,QUdpSocket对象只能通过一个 IP 地址接收数据包。

当有多个 IPv4 地址可用时,QUdpSocket对象会选择其中一个地址来接收数据包。这个选择通常由操作系统或网络栈决定,并且可能会受到各种因素的影响,例如网络接口的优先级、路由表等。

因此,使用QHostAddress::AnyIPv4参数可以让QUdpSocket对象绑定到本机的所有 IPv4 地址,但实际上它只能通过其中一个地址接收数据包。具体使用哪个地址取决于操作系统和网络环境。

二、遇到的UDP通信的问题参考

查看所有端口

netstat -ano

查看某个端口

netstat -ano | findstr 端口号

查看TCP的socket信息

netstat -an | find "TCP"

查看UDP的socket信息

netstat -an | find "UDP"

关于QT UDP组播的几个问题https://blog.csdn.net/tom06/article/details/52163665?spm=1001.2014.3001.5506

UDP多播/组播通信,同一局域网下的两台机器通信接收不到数据https://blog.csdn.net/qq_43290013/article/details/117288296?spm=1001.2014.3001.5506

QT读取网卡列表多网卡绑定组播网卡https://blog.csdn.net/qq_30727593/article/details/127441711?spm=1001.2014.3001.5506

三、效果与代码

1、在.pro文件中添加如下内容:

QT += network

2、在.h文件中添加串口所用的头文件

#include <QUdpSocket> #include <QNetworkInterface>

3、添加一个QUdpSocket* socket的类成员,并在.cpp中实例化对象:

socket = new QUdpSocket;

4、扫描可用网口

QList<QNetworkInterface> interfaceList = QNetworkInterface::allInterfaces(); foreach (QNetworkInterface nif, interfaceList) { // 检查网卡是否有效并已经启用 if (nif.isValid() && nif.flags().testFlag(QNetworkInterface::IsUp)) { // 将已经启用的网卡名称添加到列表中 enabledInterfaceList.append(nif); QList<QNetworkAddressEntry> entries = nif.addressEntries(); foreach (QNetworkAddressEntry entry, entries) { if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) { if(entry.ip().toString() == localIP){ index++; }; } } } }

5、对组播进行设置(可以忽略)

//组播的数据的生存期,数据报没跨1个路由就会减1.表示多播数据报只能在同一路由下的局域网内传播 socket->setSocketOption(QAbstractSocket::MulticastTtlOption,1); //1是允许loopback模式(自发自收),0是阻止。 socket->setSocketOption(QAbstractSocket::MulticastLoopbackOption, true);

6、初始化组播设置

int MainWindow::init_group(QUdpSocket *socket, QNetworkInterface &currentInterface, QHostAddress &localAddress, QHostAddress &targetAddress, int localPort) { if (targetAddress.isMulticast()) {//isMulticast()判断是否是组播地址 if (localPort == 0) {//判断是否指定本地端口 //bind(localAddress, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)可以换成bind(QHostAddress::AnyIPv4, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint) if (socket->bind(localAddress, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) { socket->setMulticastInterface(currentInterface); if (socket->joinMulticastGroup(targetAddress, currentInterface)) { qDebug() << "未指定本地端口,加入组播成功"; return 1; } else { qDebug() << "未指定本地端口,加入组播失败"; return -1; } } else { qDebug() << "未指定本地端口,绑定失败"; return -1; } } else { if (socket->bind(localAddress, localPort, QUdpSocket::ShareAddress | QUdpSocket::ReuseAddressHint)) { socket->setMulticastInterface(currentInterface); if (socket->joinMulticastGroup(targetAddress, currentInterface)) { qDebug() << "指定本地端口,加入组播成功"; return 1; } else { qDebug() << "指定本地端口,加入组播失败"; return -1; } } else { qDebug() << "指定本地端口,绑定失败"; return -1; } } } else { qDebug() << "目标地址不是组播地址"; return -1; } // 如果执行到这里,说明没有通过任何返回语句,应该是一个错误 qDebug() << "init_group 函数执行路径错误"; return -1; // 或者抛出异常,取决于您希望如何处理这种情况 }

1、QUdpSocket::ShareAddress

  • 这个选项告诉操作系统允许多个QUdpSocket对象绑定到同一个地址和端口上。在默认情况下,操作系统可能会阻止多个 socket 绑定到相同的地址和端口,但是如果设置了ShareAddress选项,Qt 会尝试在可能的情况下共享这个地址和端口。
  • 在实际应用中,如果需要多个QUdpSocket对象同时监听相同的地址和端口,可以使用ShareAddress选项来避免绑定失败的问题。
  • 想象一下你和朋友们想在同一个电话号码上收发短信。默认情况下,操作系统可能会阻止多个程序或者多个QUdpSocket对象同时使用同一个网络地址(IP 地址)和端口号。但是如果你打开了ShareAddress选项,就像是你和朋友们一起共享一个电话号码,大家可以同时收发信息。
  • 这个选项让多个QUdpSocket对象可以在同一个网络地址和端口上工作,而不会相互干扰或者造成绑定失败的问题。

2、QUdpSocket::ReuseAddressHint

  • 这个选项告诉操作系统允许在 UDP Socket 关闭之后,立即重新使用相同的地址和端口。如果没有设置这个选项,操作系统会在QUdpSocket关闭后一段时间内保持端口的占用状态,这可能会导致稍后尝试重新绑定失败。
  • 设置ReuseAddressHint可以避免在关闭一个QUdpSocket后立即重新绑定时遇到Address already in use的错误。
  • 想象一下你用一个电话号码打电话,然后挂了电话。在一段时间内,电话号码可能会被暂时保留,不允许其他人再用。这就好比默认情况下,当一个QUdpSocket关闭后,操作系统可能会暂时保留使用的网络地址和端口号,不让其他程序立即使用。
  • 如果你设置了ReuseAddressHint,就像是告诉操作系统,“我关掉电话后,如果其他人想用这个电话号码,可以立刻用,不用等。” 这个选项允许在一个QUdpSocket关闭后,立即重新使用相同的网络地址和端口号,而不会遇到“地址已经被占用”的错误。

总结:使用后可以重复ip和端口

7、发送消息

socket->writeDatagram(str,targetAddress,targetPort);

8、接收消息

//接收消息 connect(udpSocket,&QUdpSocket::readyRead,this,[&](){ QByteArray datagram; datagram.resize(udpSocket->pendingDatagramSize()); udpSocket->readDatagram(datagram.data(), datagram.size()); ui->recvTextEdit->append(datagram); });

9、退出组播

int MainWindow::exit_group(QUdpSocket *socket ,QNetworkInterface &currentInterface, QHostAddress &targetAddress) { if(socket->leaveMulticastGroup(targetAddress,currentInterface)){ socket->abort(); qDebug() << "退出组播成功"; return 1; }else{ qDebug() << "退出组播失败"; return -1; } }

四、多网卡场景下组播bind与setMulticastInterface的必要性

在多网卡情况下,只要bind绑定了IP地址和joinMulticastGroup加入了组播就用不上setMulticastInterface设置网卡函数,那为什么有setMulticastInterface函数,作用是什么呢?

场景1:已绑定具体IP(接口已确定)

QUdpSocket socket; QNetworkInterface eth0 = QNetworkInterface::interfaceFromName("eth0"); QHostAddress eth0IP = eth0.addressEntries().first().ip(); // 绑定具体IP地址 socket.bind(eth0IP, 12345, QUdpSocket::ShareAddress); // 已指定接口 // 加入组播组 socket.joinMulticastGroup(QHostAddress("224.1.1.1")); // ✅ 不需要setMulticastInterface

场景2:绑定所有接口但需指定接收接口

QUdpSocket socket; // 绑定所有接口 socket.bind(QHostAddress::Any, 12345, QUdpSocket::ShareAddress); // 加入组播组(未指定接口) socket.joinMulticastGroup(QHostAddress("224.1.1.1")); // ❌ 系统可能随机选择接口 // ✅ 需要使用setMulticastInterface指定接口 socket.setMulticastInterface(eth0);

五、得到当前真实网卡IP

QString Widget::get_current_IP() { QList<QNetworkInterface> interfaceList = QNetworkInterface::allInterfaces(); foreach (QNetworkInterface nif, interfaceList) { // 检查网卡是否有效并已经启用 if (nif.isValid() && nif.flags().testFlag(QNetworkInterface::IsUp) && nif.type() == QNetworkInterface::Ethernet && (nif.humanReadableName().contains("Realtek", Qt::CaseInsensitive) || nif.humanReadableName().contains("以太网", Qt::CaseInsensitive))) { // 找出对应的IP QList<QNetworkAddressEntry> entries = nif.addressEntries(); foreach (QNetworkAddressEntry entry, entries) { if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) { ui->nif_config->setText(entry.ip().toString()); return entry.ip().toString(); qDebug() << "Interface:" << nif.name() << "IP:" << entry.ip().toString(); } } } } return ""; }

ai方案

QString Widget::get_current_IP() { QStringList validIps; const QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces(); for (const QNetworkInterface &interface : interfaces) { // 1. 跳过非物理网卡或非以太网类型的接口 if (!interface.isValid() || interface.type() != QNetworkInterface::Ethernet || interface.hardwareAddress().isEmpty()) { // 硬件地址为空可能是虚拟网卡 continue; } // 2. 确认网卡状态:已启用、未回环、已连接 const bool isUp = interface.flags().testFlag(QNetworkInterface::IsUp); const bool isRunning = interface.flags().testFlag(QNetworkInterface::IsRunning); // 关键:检查物理连接状态 const bool isLoopback = interface.flags().testFlag(QNetworkInterface::IsLoopBack); if (!isUp || !isRunning || isLoopback) { continue; } // 3. 只处理有线以太网接口 if (interface.type() != QNetworkInterface::Ethernet) { continue; } // 4. 获取所有IPv4地址(已确认是有线连接) for (const QNetworkAddressEntry &entry : interface.addressEntries()) { const QHostAddress ip = entry.ip(); // 5. 只处理IPv4地址并排除特殊地址 if (ip.protocol() != QAbstractSocket::IPv4Protocol || ip.isLoopback() || ip.isMulticast() || ip.isLinkLocal()) { // 169.254.x.x continue; } const QString ipStr = ip.toString(); validIps << ipStr; qDebug() << "Found wired IP:" << ipStr << "on interface:" << interface.name() << "(" << interface.hardwareAddress() << ")"; } } // 6. 返回处理逻辑 if (!validIps.isEmpty()) { // 优选公共IP地址 for (const QString &ipStr : validIps) { QHostAddress addr(ipStr); if (addr.isGlobal()) { return ipStr; // 优先返回公共IP } } // 没有公共IP时返回第一个找到的IP return validIps.first(); } return QString(); // 返回空字符串表示未找到 }

六、动态IP (定时器刷新)

1、创建定时器

timer = new MyTimer; timer->start(FLASH_NETWORK_TIME); connect(timer,&MyTimer::timeout,this,&MainWindow::slt_scanNetwork);

2、实现函数 slt_scanNetwork

void MainWindow::slt_scanNetwork() { //----------有网口热插拔---------- quint8 validInterFaceCount = 0;//有效网卡数量 // 获取所有网络接口 QList<QNetworkInterface> interfaces = QNetworkInterface::allInterfaces(); foreach (QNetworkInterface nif, interfaces) { // 检查网卡是否有效并已经启用 if (nif.isValid() && nif.flags().testFlag(QNetworkInterface::IsUp)) { validInterFaceCount++; } } if(validInterFaceCount == netlist.size()){ return; }else{ QString old = ui->netInfCombo->currentText(); emit sgl_flashNet(netlist,ui->netInfCombo); if(old.isEmpty()){ return; } if(ui->netInfCombo->findText(old) != -1){ ui->netInfCombo->setCurrentText(old); }else{ emit sgl_exitUniCast(); } // 有本地IP就加载到本地IP上 if(ui->netInfCombo->findText(get_current_IP()) != -1){ ui->netInfCombo->setCurrentText(get_current_IP()); } } }

3、实现刷新网口函数 init_or_flash_NetInterfaceInfoToCombox

connect(this,&Widget::sgl_flashNet,this,[&](QList<QNetworkInterface> &list, QComboBox *combox){ myUdpTool->init_or_flash_NetInterfaceInfoToCombox(list,combox); });
void MyUdpTools::init_or_flash_NetInterfaceInfoToCombox(QList<QNetworkInterface> &list, QComboBox *combox) { if(!list.isEmpty()){ list.clear(); } if(combox->count() != 0){ combox->clear(); } QList<QNetworkInterface> interfaceList = QNetworkInterface::allInterfaces(); foreach (QNetworkInterface nif, interfaceList) { // 检查网卡是否有效并已经启用 if (nif.isValid() && nif.flags().testFlag(QNetworkInterface::IsUp)) { // 将已经启用的网卡名称添加到列表中 list.append(nif); QList<QNetworkAddressEntry> entries = nif.addressEntries(); foreach (QNetworkAddressEntry entry, entries) { if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) { combox->addItem(entry.ip().toString()); } } } } }

七、自定义UDP工具类

MyUdpTools.h

#ifndef MYUDPTOOLS_H #define MYUDPTOOLS_H #include <QObject> #include <QThread> #include <QComboBox> #include <QUdpSocket> #include <QMessageBox> #include <QApplication> #include <QNetworkDatagram> #include <QNetworkInterface> class MyUdpTools : public QObject { Q_OBJECT public: explicit MyUdpTools(QObject *parent = nullptr); ~MyUdpTools(); // 单播相关方法 int setupUnicast(QString currentNetInterfacrAddress, quint16 port = 0); void sendUnicastData(QString targetAddress,quint16 port,const QByteArray &data); int exitUnicast(); // 组播相关方法 int setupMulticast(QNetworkInterface netInterface,QString currentNetInterfacrAddress,QString groupAddress, quint16 port = 0); void sendMulticastData(QString targetAddress,quint16 port,const QByteArray &data); int exitMulticast(); // 广播相关方法 int setupBroadcast(QString currentNetInterfacrAddress,QString targetAddress,quint16 port = 0); void sendBroadcastData(QString targetAddress,quint16 port,const QByteArray &data); int exitBroadcast(); //添加或刷新combox的网卡信息 void init_or_flash_NetInterfaceInfoToCombox(QList<QNetworkInterface> &list,QComboBox *combox); signals: void sig_RecvData(QByteArray data); private slots: void processPendingDatagrams();//接收数据槽函数 void handleSocketError(QAbstractSocket::SocketError socketError);//Udp错误日志 private: QThread *thread; QHostAddress localAddress;//本机当前网卡ip QUdpSocket *unicastSocket;//单播 quint16 unicastPort_Local;//单播本地端口 quint16 unicastPort_Target;//单播目标端口 QHostAddress targetAddress_Unicast;//单播目标地址 QUdpSocket *multicastSocket;//组播 QHostAddress multicastGroupAddress;//组播地址 quint16 multicastPort_Local;//组播本地端口 quint16 multicastPort_Target;//组播目标端口 QUdpSocket *broadcastSocket;//广播 QHostAddress broadcastGroupAddress;//广播地址 quint16 broadcastPort_Local;//广播本地端口 quint16 broadcastPort_Target;//广播目标端口 }; #endif // MYUDPTOOLS_H

MyUdpTools.cpp

#include "myudptools.h" MyUdpTools::MyUdpTools(QObject *parent) : QObject(parent),unicastSocket(nullptr),multicastSocket(nullptr),broadcastSocket(nullptr) { qInfo() << "主线程:" << QThread::currentThreadId(); qRegisterMetaType<QNetworkInterface>("QNetworkInterface"); thread = new QThread(); moveToThread(thread); //应用程序关闭后触发 connect(qApp,&QApplication::aboutToQuit,thread, &QThread::quit); //线程退出后触发 connect(thread, &QThread::finished, thread, &QThread::deleteLater); connect(thread,&QThread::finished,this,&MyUdpTools::deleteLater); // 在子线程中直接调用函数 // QMetaObject::invokeMethod(this, "init_or_flash_NetInterfaceInfoToCombox", Qt::QueuedConnection); //启动线程 thread->start(); } MyUdpTools::~MyUdpTools() { if(unicastSocket){ delete unicastSocket; } if(multicastSocket){ delete multicastSocket; } if(broadcastSocket){ delete broadcastSocket; } } //===========================单播相关方法实现=========================== int MyUdpTools::setupUnicast(QString currentNetInterfacrAddress, quint16 port) { if (!unicastSocket) { connect(unicastSocket, &QUdpSocket::readyRead, this, &MyUdpTools::processPendingDatagrams); unicastSocket = new QUdpSocket(this);//将父对象设置为当前对象 connect(unicastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(handleSocketError(QAbstractSocket::SocketError))); qInfo() << "创建单播socket"; } localAddress = QHostAddress(currentNetInterfacrAddress); unicastPort_Local = port; if(port == 0){ if (!unicastSocket->bind(localAddress,QUdpSocket::ReuseAddressHint|QUdpSocket::ShareAddress)) { qCritical() << "单播绑定失败:" << unicastSocket->errorString(); return -1; } }else{ if (!unicastSocket->bind(localAddress, unicastPort_Local,QUdpSocket::ReuseAddressHint|QUdpSocket::ShareAddress)) { qCritical() << "单播绑定失败:" << unicastSocket->errorString(); return -1; } } qInfo() << "单播连接成功"; return 1; } void MyUdpTools::sendUnicastData(QString targetAddress, quint16 port, const QByteArray &data) { if(unicastSocket){ targetAddress_Unicast = QHostAddress(targetAddress); unicastPort_Target = port; if(unicastSocket->writeDatagram(data,targetAddress_Unicast,unicastPort_Target) == -1){ qCritical() << "发送单播数据失败:" << unicastSocket->errorString(); } } } int MyUdpTools::exitUnicast() { if (unicastSocket) { unicastSocket->close(); unicastSocket = nullptr; qCritical() << "退出成功"; return 1; }else{ qCritical() << "已退出单播,无需再退出"; return -1; } } //===========================组播相关方法实现=========================== int MyUdpTools::setupMulticast(QNetworkInterface netInterface,QString currentNetInterfacrAddress, QString groupAddress, quint16 port) { if(!multicastSocket){ multicastSocket = new QUdpSocket(this); connect(multicastSocket, &QUdpSocket::readyRead, this, &MyUdpTools::processPendingDatagrams); connect(multicastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(handleSocketError(QAbstractSocket::SocketError))); qInfo() << "创建组播socket"; } localAddress = QHostAddress(currentNetInterfacrAddress); multicastGroupAddress = QHostAddress(groupAddress); multicastPort_Local = port; if(!multicastGroupAddress.isMulticast()){ qWarning() << "不是组播地址"; return -1; } if(port == 0){ if(!multicastSocket->bind(localAddress,QUdpSocket::ReuseAddressHint|QUdpSocket::ShareAddress)){ qCritical() << "组播绑定失败:" << multicastSocket->errorString(); return -1; } }else{ if(!multicastSocket->bind(localAddress,multicastPort_Local,QUdpSocket::ReuseAddressHint|QUdpSocket::ShareAddress)){ qCritical() << "组播绑定失败:" << multicastSocket->errorString(); return -1; } } multicastSocket->setMulticastInterface(netInterface); if(!multicastSocket->joinMulticastGroup(multicastGroupAddress,netInterface)){ qCritical() << "加入组播失败:" << multicastSocket->errorString(); return -1; } qInfo() << "组播连接成功"; return 1; } void MyUdpTools::sendMulticastData(QString targetAddress, quint16 port, const QByteArray &data) { if(multicastSocket){ multicastGroupAddress = QHostAddress(targetAddress); multicastPort_Target = port; if(multicastSocket->writeDatagram(data,multicastGroupAddress,multicastPort_Target) == -1){ qCritical() << "发送组播数据失败:" << multicastSocket->errorString(); } } } int MyUdpTools::exitMulticast() { if (multicastSocket) { multicastSocket->leaveMulticastGroup(multicastGroupAddress); // 确保在退出组播后关闭套接字 multicastSocket->close(); // 删除套接字指针 multicastSocket = nullptr; qInfo() << "退出组播成功"; return 1; }else{ qCritical() << "已退出组播,无需再退出"; return -1; } } //===========================广播相关方法实现=========================== int MyUdpTools::setupBroadcast(QString currentNetInterfaceAddress,QString targetAddress, quint16 port) { if (!broadcastSocket) { broadcastSocket = new QUdpSocket(this);//将父对象设置为当前对象 connect(broadcastSocket, &QUdpSocket::readyRead, this, &MyUdpTools::processPendingDatagrams); connect(broadcastSocket, SIGNAL(error(QAbstractSocket::SocketError)),this, SLOT(handleSocketError(QAbstractSocket::SocketError))); qInfo() << "创建广播socket"; } localAddress = QHostAddress(currentNetInterfaceAddress); broadcastPort_Local = port; broadcastGroupAddress = QHostAddress(targetAddress); if(!broadcastGroupAddress.isBroadcast()){ qCritical() << "不是广播地址"; return -1; } if(port == 0){ if (!broadcastSocket->bind(localAddress,QUdpSocket::ReuseAddressHint|QUdpSocket::ShareAddress)) { qCritical() << "广播绑定失败:" << broadcastSocket->errorString(); return -1; } }else{ if (!broadcastSocket->bind(localAddress, broadcastPort_Local,QUdpSocket::ReuseAddressHint|QUdpSocket::ShareAddress)) { qCritical() << "广播绑定失败:" << broadcastSocket->errorString(); return -1; } } qInfo() << "连接成功"; return 1; } void MyUdpTools::sendBroadcastData(QString targetAddress, quint16 port, const QByteArray &data) { if(broadcastSocket){ broadcastGroupAddress = QHostAddress(targetAddress); broadcastPort_Target = port; if(broadcastSocket->writeDatagram(data,broadcastGroupAddress,broadcastPort_Target) == -1){ qCritical() << "发送广播数据失败:" << broadcastSocket->errorString(); } } } int MyUdpTools::exitBroadcast() { if (broadcastSocket) { broadcastSocket->close(); broadcastSocket = nullptr; qInfo() << "退出成功"; return 1; }else{ qDebug() << "已退出广播,无需再退出"; return -1; } } //===========================添加或刷新combox的网卡信息函数实现=========================== void MyUdpTools::init_or_flash_NetInterfaceInfoToCombox(QList<QNetworkInterface> &list, QComboBox *combox) { list.clear(); combox->clear(); QList<QNetworkInterface> interfaceList = QNetworkInterface::allInterfaces(); foreach (QNetworkInterface nif, interfaceList) { // 检查网卡是否有效并已经启用 if (nif.isValid() && nif.flags().testFlag(QNetworkInterface::IsUp)) { // 将已经启用的网卡名称添加到列表中 list.append(nif); QList<QNetworkAddressEntry> entries = nif.addressEntries(); foreach (QNetworkAddressEntry entry, entries) { if (entry.ip().protocol() == QAbstractSocket::IPv4Protocol) { combox->addItem(entry.ip().toString()); } } } } } //===========================接收数据槽函数实现=========================== void MyUdpTools::processPendingDatagrams() { QUdpSocket *socket = qobject_cast<QUdpSocket *>(sender());//sender()发送信号的对象的指针 if (!socket) return; while (socket->hasPendingDatagrams()) { #if 1 QByteArray datagram; datagram.resize(socket->pendingDatagramSize()); socket->readDatagram(datagram.data(), datagram.size()); qDebug() << "Received Data:" << datagram; emit sig_RecvData(datagram); #else QNetworkDatagram datagram = socket->receiveDatagram(); QByteArray data = datagram.data(); qDebug() << "Received Data:" << datagram; emit sig_RecvData(data); #endif } } //===========================Udp错误日志信息=========================== void MyUdpTools::handleSocketError(QAbstractSocket::SocketError socketError) { QUdpSocket *socket = qobject_cast<QUdpSocket *>(sender()); if (!socket) return; switch (socketError) { case QAbstractSocket::HostNotFoundError: qCritical() << "Host not found error:" << socket->errorString(); break; case QAbstractSocket::ConnectionRefusedError: qCritical() << "Connection refused error:" << socket->errorString(); break; case QAbstractSocket::DatagramTooLargeError: qCritical() << "Datagram too large error:" << socket->errorString(); break; default: qCritical() << "Socket error:" << socket->errorString(); break; } }

注意:

为什么new QThread();不能指定父对象为this?

如果指定父对象为this的话,QThread的生命周期就由其父对象MyUdpTools管理,不是由QThread直接管理,这不是推荐的做法。

不同线程之间的通信要用信号与槽进行通信。

在子线程中不能调用函数,会影响线程安全,可以在主线程中使用函数QMetaObject::invokeMethod 使得 子线程能直接调用函数。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/8/19 16:29:26

C/C++开发工具CLion v2022.3全新发布——支持C++ 20

CLion是一款专为开发C及C所设计的跨平台IDE。它是以IntelliJ为基础设计的&#xff0c;包含了许多智能功能来提高开发人员的生产力。这种强大的IDE帮助开发人员在Linux、OS X和Windows上来开发C/C&#xff0c;同时它还使用智能编辑器来提高代码质量、自动代码重构并且深度整合CM…

作者头像 李华
网站建设 2026/8/19 16:29:14

激励错位下的重复博弈:信息与契约设计如何驱动长期合作

1. 项目概述&#xff1a;当激励错位的代理反复相遇在现实世界的合作与博弈中&#xff0c;一个核心且无处不在的难题是&#xff1a;当参与各方的目标不完全一致&#xff0c;甚至存在冲突时&#xff0c;如何设计一套有效的规则&#xff0c;使得长期合作成为可能&#xff1f;这正是…

作者头像 李华
网站建设 2026/8/19 16:28:01

免费离线OCR软件Umi-OCR怎么用?从截图识别到PDF文字提取的完整实测

免费离线OCR软件Umi-OCR怎么用&#xff1f;从截图识别到PDF文字提取的完整实测 【免费下载链接】Umi-OCR OCR software, free and offline. 开源、免费的离线OCR软件。支持截屏/批量导入图片&#xff0c;PDF文档识别&#xff0c;排除水印/页眉页脚&#xff0c;扫描/生成二维码。…

作者头像 李华