当前位置: 首页 > news >正文

C++|UDP通讯使用总结

最近开发了一个小软件,应项目经理强烈要求,通讯是采用UDP,下面作为总结为大家分享一下~

开发环境:Qt 6.8.2

本来我是打算直接用QUdpSocker开发的,想着纯Qt项目,直接使用Qt自带的功能,开发起来肯定会快,果真开发起来很快,但是我发现,使用QUdpSocket自带的消息,当连接两台设备的时候,接收消息就会不及时

connect(m_pUdpSocket, &QUdpSocket::readyRead, this, &UdpSocketManger::OnReadyUdpData);void UdpSocketManger::OnReadyUdpData()
{while(m_pUdpSocket->hasPendingDatagrams()){QNetworkDatagram datagram = m_pUdpSocket->receiveDatagram(); //接收到一条完整的数据包if(!datagram.isValid()) continue;QString sOutIP = "";if(isLocalAddress(datagram.senderAddress(), sOutIP)){continue; //本机IP地址消息,不处理}{//使用锁机制添加处理数据std::lock_guard<std::mutex> lock(m_mutexQueue);m_dequeOriginalData.push(UdpPacket{sOutIP, datagram.data()}); //尾部追加数据}}
}

我还在这里使用开线程的方式接收处理原始数据,如果我要是在OnReadyUdpData这个函数中直接处理,那肯定是会卡死界面的。这个方法果真不行,后来我有尝试将OnReadyUpData这个函数中逻辑直接放到线程中使用,会比使用connect消息的方式快一些,也仅仅是快一些而已。

果断放弃了QUDP,转而使用C++原生的UDP通讯,虽然写起来比较麻烦,但是效率高呀!

C++原生的UDP通讯直接系统调用,延迟更低,吞吐量更高,即使我将QUdp优化到极致,也无法高性能的带动50台设备,毕竟Qt UDP被封装了一层。

下面我为大家分享C++原生UDP,简单版本,仅限于IPV4(IPV6兼容这里就不讲解了,否则太乱),单线程接收数据,单线程处理数据,吞吐量<100肯定是没问题的。

C++ UDP实现逻辑

1:创建UDP套接字

m_serverSocket = socket(AF_INET, SOCK_DGRAM, 0);
if (m_serverSocket < 0)
{safeCloseSocket("创建UDP 套接字 失败!");return false;
}

2:设置非阻塞模式

u_long mode = 1; // 1=非阻塞,0=阻塞
if(ioctlsocket(m_serverSocket, FIONBIO, &mode) != 0)
{safeCloseSocket("设置非阻塞模式失败!");return false;
}

3:禁止广播回环

int loop = 0;
if (setsockopt(m_serverSocket, IPPROTO_IP, IP_MULTICAST_LOOP, reinterpret_cast<const char*>(&loop), sizeof(loop)) < 0)
{qDebug() << "Failed to disable loopback";
}

4:启动UDP广播

int broadcastEnable = 1;
if (setsockopt(m_serverSocket, SOL_SOCKET, SO_BROADCAST, reinterpret_cast<const char*>(&broadcastEnable), sizeof(broadcastEnable)))
{safeCloseSocket("UDP通讯,启动广播策略,失败!");return false;
}

5:绑定端口

sockaddr_in serverAddr{};
memset(&serverAddr, 0, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
//serverAddr.sin_addr.s_addr = INADDR_ANY; // 绑定所有网卡
std::string sIP = m_sLocalIP.toStdString();
inet_pton(AF_INET, sIP.c_str(), &serverAddr.sin_addr);
serverAddr.sin_port = htons(m_nPort);
if (bind(m_serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
{safeCloseSocket("UDP通讯,绑定端口失败!");return false;
}

注意:在我的这段代码中屏蔽了代码:serverAddr.sin_addr.s_addr = INADDR_ANY; 那是因为当我的PC机上存在虚拟网卡时,使用UDP服务端广播的消息无法被发送出去

6:开启数据监听线程

m_bDataProcessing = true; //开启数据监听处理
m_threadReceived = std::thread(&UdpSocketManger::ThreadReceiveData, this, m_serverSocket);

m_threadReceived定义:std::thread m_threadReceived

7:开启数据工作线程

因为数据量过大,如果直接在recvfrom中处理数据,可能会影响数据接收的效率,那么在启动监听线程时,同步启动工作线程

m_threadWorker = std::thread(&UdpSocketManger::ThreadWorker, this);

8:监听线程实现

void UdpSocketManger::ThreadReceiveData(int sockfd)
{char buffer[BUFFER_SIZE] = { '\0' }; //接收数据缓冲区while(m_bDataProcessing){sockaddr_storage  clientAddr;
#ifdef _WIN32int clientAddrLen = sizeof(clientAddr);
#elsesocklen_t clientAddrLen = sizeof(clientAddr);
#endifssize_t recvLen = recvfrom(sockfd, buffer, BUFFER_SIZE, 0, reinterpret_cast<sockaddr*>(&clientAddr), &clientAddrLen);if(recvLen <= 0){std::this_thread::sleep_for(std::chrono::milliseconds(10));continue; //无效数据不处理}// 解析IPchar ipStr[INET6_ADDRSTRLEN] = {0};auto* addr4 = reinterpret_cast<sockaddr_in*>(&clientAddr);inet_ntop(AF_INET, &addr4->sin_addr, ipStr, sizeof(ipStr));QString clientIP = QString::fromLatin1(ipStr);//过滤自身IP数据if(m_setLocalIPString.contains(clientIP)){continue;}// 关键转换:char* → QByteArrayQByteArray data(buffer, recvLen); // 直接构造,避免额外拷贝{std::lock_guard<std::mutex> lock(m_mutexQueue);m_dequeOriginalData.push(UdpPacket{clientIP,data}); //尾部追加数据}}qDebug() << "线程《ThreadReceiveData》,安全结束!";
}

在我的线程实现中,添加了过滤自身IP数据,保证每次处理的有效数据都是非本机传入的。

有人会问:不是已经设置了禁止回环怎么还会接收到本机的消息呢?

当我们在发送一个UDP广播包时,操作系统的网络协议栈会做两件事:

第一件事:将数据包通过物理网卡发送到网络中

第二件事:内向回环,同时协议栈会将这个数据包复制一份,直接“饶回”给本机上所有正在监听目标端口的套接字。

而且,项目经理设计的不合理,服务端和客户端都绑定了一个端口号,能不接收到自己的消息才怪!

8:获取本机IP地址

那么在我的UdpSocketManager构造函数中就需要首先获取本地的地址,用于线程进行对比

为了简便使用的是Qt方式:

QSet<QString> m_setLocalIPString; //缓存本地IP地址字符串
for (const QHostAddress &addr : QNetworkInterface::allAddresses()) {m_setLocalIPString.insert(addr.toString());
}

9:工作线程实现

void UdpSocketManger::ThreadWorker()
{while(m_bDataProcessing){// 从队列获取数据UdpPacket packet;{std::unique_lock<std::mutex> lock(m_mutexQueue);if (!m_bDataProcessing) break;if(!m_dequeOriginalData.empty()){packet= std::move(m_dequeOriginalData.front()); //移动而非拷贝数据m_dequeOriginalData.pop(); //剔除第一条数据}}// 处理数据(示例:打印客户端信息)if(!packet.dataArray.isEmpty()){//检查当前接收的数据是否有效if(this->JudgeValidData(packet.dataArray)){//数据有效,此时处理有效数据this->ProcessingValidData(packet.sSenderIP, packet.dataArray);}}std::this_thread::sleep_for(std::chrono::milliseconds(10));}qDebug() << "线程《ThreadWorker》,安全结束!";
}

10:停止线程

//1: 关闭线程标识
m_bDataProcessing = false;
//2: 等待线程结束
if (m_threadReceived.joinable()) 
{m_threadReceived.join();
}
if (m_threadWorker.joinable()) 
{m_threadWorker.join();
}
//3: 关闭socket
safeCloseSocket();
//4: 清理所有线程数据
clearTotalThreadData();

11:安全关闭线程

void UdpSocketManger::safeCloseSocket(QString sLogTips)
{if (!sLogTips.isEmpty()){
#ifdef _WIN32qDebug() << sLogTips << "| 错误码:" << WSAGetLastError();
#elseqDebug() << sLogTips << "| 错误:" <<strerror(errno);
#endif}if(m_serverSocket != -1){
#ifdef _WIN32closesocket(m_serverSocket);
#elseclose(m_serverSocket);
#endifm_serverSocket = -1;  // 标记为已关闭}
}

以上就是使用C++的UDP进行通讯,相比较QUDP来说,性能更高。我最开始使用的是QUDP可能是因为数据量大,导致连接设备过多UDP处理不过来,就换成了C++的原生UDP,效率果然提升了。

我是糯诺诺米团,一名C++开发程序媛~

http://www.xdnf.cn/news/1366129.html

相关文章:

  • HTML应用指南:利用GET请求获取MSN 天气数据并可视化
  • [系统架构设计师]应用数学(二十一)
  • list容器的使用
  • GNN:用MPNN(消息传递神经网络)落地最短路径问题模型训练全流程
  • 用 GSAP + ScrollTrigger 打造沉浸式视频滚动动画
  • 【Day 33】Linux-Mysql日志
  • DDR3入门系列(二)------DDR3硬件电路及Xilinx MIG IP核介绍
  • linux 正则表达式学习
  • 使用 gemini 来分析 github 项目
  • 安卓11 12系统修改定制化_____修改固件 默认给指定内置应用系统级权限
  • 大模型的思考方式
  • Java全栈开发实战:从Spring Boot到Vue3的项目实践
  • ZKmall开源商城多端兼容实践:鸿蒙、iOS、安卓全平台适配的技术路径
  • 8.25作业
  • [MH22D3开发笔记]2. SPI,QSPI速度究竟能跑多快,双屏系统的理想选择
  • Linux笔记9——shell编程基础-3
  • Tesseract OCR之页面布局分析
  • Linux系统的网络管理(一)
  • c# 读取xml文件内的数据
  • 网络编程-HTTP
  • zookeeper-znode解析
  • 【动态规划】309. 买卖股票的最佳时机含冷冻期及动态规划模板
  • 深入浅出 ArrayList:从基础用法到底层原理的全面解析(中)
  • 【C语言16天强化训练】从基础入门到进阶:Day 11
  • 信号处理的核心机制:从保存、处理到可重入性与volatile
  • 系统架构设计师-计算机系统存储管理的模拟题
  • 【数据结构】栈和队列——队列
  • AR远程协助:能源电力行业智能化革新
  • 数据库迁移幂等性介绍(Idempotence)(Flyway、Liquibase)ALTER、ON DUPLICATE
  • 05 开发环境和远程仓库Gitlab准备