当前位置: 首页 > 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.dtcms.com/a/349398.html

相关文章:

  • Fluent Bit系列:字符集转码测试(下)
  • Dify 从入门到精通(第 55/100 篇):Dify 的模型微调(进阶篇)
  • Devops之Jenkins:Jenkins服务器中的slave节点是什么?我们为什么要使用slave节点?如何添加一个windows slave节点?
  • 如何监控ElasticSearch的集群状态?
  • Fluent Bit系列:字符集转码测试(上)
  • LengthFieldBasedFrameDecoder 详细用法
  • Error ratio tests for 200 Gb/s per lane ISLs using PMAmeasurements
  • 李沐-第十章-实现Seq2SeqAttentionDecoder时报错
  • 什么是事件循环(Event Loop)?浏览器和 Node.js 中的事件循环有什么区别?
  • springboot整合druid(多数据源配置)
  • Python_occ 学习记录 | 阵列
  • 李沐-第十章-训练Seq2SeqAttentionDecoder报错
  • 十九、云原生分布式存储 CubeFS
  • 剧本杀APP系统开发:打造多元化娱乐生态的先锋力量
  • Go编写的轻量文件监控器. 可以监控终端上指定文件夹内的变化, 阻止删除,修改,新增操作. 可以用于AWD比赛或者终端应急响应
  • TensorFlow深度学习实战(34)——TensorFlow Probability
  • GO学习记录八——多文件封装功能+redis使用
  • Node.js(2)—— Buffer
  • 安卓Android低功耗蓝牙BLE连接异常报错133
  • Docker Compose 部署 Elasticsearch 8.12.2 集成 IK 中文分词器完整指南
  • Go初级三
  • 上海AI实验室突破扩散模型!GetMesh融合点云与三平面,重塑3D内容创作
  • 少儿舞蹈小程序需求规格说明书
  • AutoCAD Electrical缺少驱动程序“AceRedist“解决方法
  • 【STM32】G030单片机的独立看门狗
  • ELKB日志分析平台 部署
  • 完美世界招数据仓库工程师咯
  • ArcGIS JSAPI 高级教程 - 创建渐变色材质的自定义几何体
  • three.js+WebGL踩坑经验合集(8.3):合理设置camera.near和camera.far缓解实际场景中的z-fighting叠面问题
  • 大数据平台ETL任务导入分库分表数据