[Qt网络编程]之UDP通讯的简单编程实现

hello!欢迎大家来到我的Qt学习系列之网络编程之UDP通讯的简单编程实现。希望这篇文章能对你有所帮助!!!

本篇文章的相关知识请看我的上篇文章:

http://t.csdnimg.cn/UKyeM

目录

UDP通讯

 基于主窗口的实现

 基于线程的实现


UDP通讯

        UDP数据报协议是一个面向无连接的传输层报文协议,它简单易用,不存在 TCP协议“粘包”的问题,在强调实时、主动推送的系统中,常常用 UDP协议来实现网络双方的通信。在 Qt 中,QUdpSocket 类提供了 UDP 数据报的通信支持,下面通过两个简单的例子介绍Qt下 UDP 协议的实现。

模拟网络上经常定义的数据报文结构:

字节1~45~89~1213~1617~20
定义序号小时分钟毫秒
#pragma pack(push) //保存对齐状态
#pragma pack(4) //设定为4字节对齐
struct DataStruct{unsigned int index;//序号int hour;//小时int minute;//分钟int second;//秒int msec;//毫秒
};
union NetBuffer{DataStruct data;char dataBuffer[20];
};
#pragma pack(pop) //恢复对齐状态

这里用了一个联合定义的数据缓冲区,便于进行数据报文的设置和解析。

需要在 *.pro 工程文件中添加 network 选项 :

QT +=core gui network

 基于主窗口的实现

        UDP报文的发送比较随意,可以在程序的任何需要的时候和位置发送 UDP报文,为了演示的简单,本例子中设置了主窗口的定时器,每秒钟发送一次报文。在接收的时候,响应接收端口 readyRead()信号,及时读取网络协议缓冲区的数值。

1.新建一个工程,在界面中添加两个列表部件,用于显示发送和接收的数据:

2. 在头文件中,添加包含 QNetworkInterface、QHostAddress 和 QudpSocket 模块,添加网络数据报文的结构定义。在 MainWindow 类定义中,添加需要重载的 timerEvent 定义,添加读取数据报文操作 readPendingDatagrams 定义,以及主机地址、发送和接收 socket和缓冲区定义。

#ifndef MAINWINDOW_H
#define MAINWINDOW_H#include<QMainWindow>
#include<QtNetwork/QNetworkInterface>
#include<QtNetwork/QHostAddress>
#include<QtNetwork/QUdpSocket>QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE#pragma pack(push) //保存对齐状态
#pragma pack(4) //设定为4字节对齐
struct DataStruct{unsigned int index;//序号int hour;//小时int minute;//分钟int second;//秒int msec;//毫秒
};
union NetBuffer{DataStruct data;char dataBuffer[20];
};
#pragma pack(pop) //恢复对齐状态class MainWindow : public QMainWindow
{Q_OBJECTpublic:MainWindow(QWidget *parent = nullptr);~MainWindow();void timerEvent(QTimerEvent * event);public slots:void readPendingDatagrams();private:Ui::MainWindow *ui;QHostAddress hostAddress;QUdpSocket udpSendSocket,udpRecvSocket;NetBuffer sendBuffer,recvBuffer;
};
#endif // MAINWINDOW_H

3. 在 MainWindow 的构造函数中,获取本机地址,绑定发送和接收 socket,设置响应接收 socket 接收信号的槽。

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);//通过调用静态方法获取本机IP地址QList<QHostAddress> addressList = QNetworkInterface::allAddresses();hostAddress=addressList.at(0);//网络端口绑定udpSendSocket.bind(hostAddress,7000);udpRecvSocket.bind(hostAddress,7001);//设置定时器this->startTimer(1000);//初始化发送计数器sendBuffer.data.index=0;//建立接收 socket 的连接QObject::connect(&udpRecvSocket,SIGNAL(readyRead()),this,SLOT(readPendingDatagrams()));
}

4.实现发送和接收操作,并在列表中显示。发送操作是在定时器事件响应函数中实现的,上面已经设置了每秒发送一次。数据接收是在 readPendingDatagrams()函数中实现的,当接收 socket 一有数据报文包,readPendingDatagrams()就被调用,读取网络接收到的数据,并解析显示。在这里我们用了联合的方法来解析网络数据结构,方便易用。

//在 timeEvent 中设置发送数据,并在列表中显示
void MainWindow::timerEvent(QTimerEvent * event){QTime tm = QTime::currentTime();//获取当前时间sendBuffer.data.hour=tm.hour();sendBuffer.data.minute=tm.minute();sendBuffer.data.second=tm.second();sendBuffer.data.msec=tm.msec();//调用发送数据包函数,发送数据udpSendSocket.writeDatagram (sendBuffer.dataBuffer,sizeof(sendBuffer),hostAddress,7001);QString displaystring;displaystring=QString("Index=%1 \nTime=%2:%3:%4.%5\n").arg(sendBuffer.data.index).arg(sendBuffer.data.hour,2,10,QChar('0')).arg(sendBuffer.data.minute,2,10,QChar('0')).arg(sendBuffer.data.second,2,10,QChar('0')).arg(sendBuffer.data.msec,3,10,QChar('0'));ui->listWidget->insertItem(0,displaystring);sendBuffer.data.index++;
}//在 readPendingDatagrams 槽中,接收数据并显示
void MainWindow::readPendingDatagrams (){QHostAddress sender;quint16 senderPort;//调用数据接接收函数,接收数据udpRecvSocket.readDatagram(recvBuffer.dataBuffer,sizeof (recvBuffer),&sender,&senderPort);QString displaystring;displaystring=QString("Index=%1 \nTime=%2:%3:%4.%5\n").arg (recvBuffer.data.index).arg(recvBuffer.data.hour,2,10,QChar('0')).arg(recvBuffer.data.minute,2,10,QChar('0')).arg(recvBuffer.data.second,2,10,QChar('0')).arg(recvBuffer.data.msec,3,10,QChar('0'));ui->listWidget_2->insertItem(0,displaystring);
}

 


 基于线程的实现

        基于窗口部件的 UDP通信实现,虽然简单易用,但是窗口部件主要的工作是负责处理大量的用户界面信息,当有耗时的处理过程时,会影响数据的接收,造成丢帧。通常的做法是用独立的线程负责网络数据的发送和接收,再通过窗口部件显示输出,在实时系统中这种应用特别广泛。下面的例子显示的效果和前面一致,但实现的机理是完全不同的。

1.新建工程,在工程中依次新建发送和接收线程的C++文件 sendthread. h,sendthread. epp 和 reevthread. h,recvthread. cpp:

其中sendthread.h定义:

#include <QWidget>
#include<QThread>
#include<QtNetwork/QNetworkInterface>
#include<QtNetwork/QHostAddress>
#include<QtNetwork/QUdpSocket>
#include "NetBuffer.h" //就是上文定义的数据缓冲
class sendthread :public QThread
{Q_OBJECT
public:explicit sendthread(QWidget *parent=0);
protected:void run();
private:QHostAddress hostAddress;QUdpSocket udpsendsocket;NetBuffer sendBuffer;
};
#endif // SENDTHREAD_H

在 sendthread.h中定义了线程需要用到的主机地址 hostAddress、UDPsocket 端口和发送缓冲区,定义了线程需要重载的 run()操作。在 sendthread.cpp 的构造函数中,初始化参数,获取本机地址,绑定 socket 端口:

#include "sendthread.h"sendthread::sendthread(QWidget *parent):QThread(parent)
{QList<QHostAddress> addresslist=QNetworkInterface::allAddresses();hostAddress=addresslist.at(0);udpsendsocket.bind(hostAddress,7000);sendBuffer.data.index=0;
}

然后重载实现 run()操作。这里要注意的是,由于主窗口的 ui变量是 protected 类型线程不能直接使用,需要线程通过主窗口的 displaySendData方法,将显示信息输出到界面中。

#include<QTime>
void sendthread::run(){while(true){QTime tm=QTime::currentTime();sendBuffer.data.hour=tm.hour();sendBuffer.data.minute =tm.minute();sendBuffer.data.second =tm.second();sendBuffer.data.msec=tm.msec();udpsendsocket.writeDatagram(sendBuffer.dataBuffer,sizeof(sendBuffer),hostAddress,7001);QString displaystring;displaystring=QString("Index=%1\nTime=%2:%3:%4.%5\n").arg(sendBuffer.data.index).arg(sendBuffer.data.hour,2,10,QChar('0')).arg(sendBuffer.data.minute,2,10,QChar('0')).arg(sendBuffer.data.second,2,10,QChar('0')).arg(sendBuffer.data.msec,3,10,QChar('0'));((MainWindow*)this->parent())->DisplaySendData(displaystring);sendBuffer.data.index++;this->sleep(1);}
}

其中 recvthread.h 的定义:

#include <QWidget>
#include<QThread>
#include<QtNetwork/QNetworkInterface>
#include<QtNetwork/QHostAddress>
#include<QtNetwork/QUdpSocket>
#include "NetBuffer.h"
class recvthread: public QThread
{Q_OBJECT
public:explicit recvthread(QWidget *parent=0);
protected:void run();
private:QHostAddress hostAddress;QUdpSocket udpRecvSocket;NetBuffer recvBuffer;
};

和发送线程类似,定义了主机地址 hostAddress、UDPsocket 端口和发送缓冲区,定义了需要重载的 run()操作。在构造函数中,初始化接收 socket。

recvthread::recvthread(QWidget *parent):QThread(parent)
{QList<QHostAddress> addresslist=QNetworkInterface::allAddresses();hostAddress=addresslist.at(0);udpRecvSocket.bind(hostAddress,7001);
}

在 run()中读取网络数据,并通过主窗口的 DisplayRecvData方法显示。注意这里使用了 waitForReadyRead方法以同步方式读取数据,而不是使用信号和槽的异步方法。当没有新数据到来时,线程处于挂起等待状态,当有数据到达时,立刻进入下一步处理,这种方法响应得更及时快速。

#include"mainwindow.h"
void recvthread::run(){while (true){if(udpRecvSocket.waitForReadyRead()){QHostAddress sender;quint16 senderPort;udpRecvSocket.readDatagram(recvBuffer.dataBuffer,sizeof(recvBuffer),&sender,&senderPort);QString displaystring;displaystring=QString("Index=%1\nTime=%2:%3:%4.%5\n").arg(recvBuffer.data.index).arg(recvBuffer.data.hour,2,10,QChar('0')).arg(recvBuffer.data.minute,2,10,QChar('0')).arg(recvBuffer.data.second,2,10,QChar('0')).arg(recvBuffer.data.msec,3,10,QChar('0'));((MainWindow*)this->parent())->DisplayRecvData(displaystring);}
}

2.在主窗口中,初始化发送和接收 socket 线程,定义 DisplaySendData 和 DisplayRecvData操作显示收发数据。

MainWindow::MainWindow(QWidget *parent): QMainWindow(parent), ui(new Ui::MainWindow)
{ui->setupUi(this);sendthread *sendThread=new sendthread(this);recvthread *recvTrhead=new recvthread(this);recvTrhead->start();sendThread->start();
}void MainWindow::DisplaySendData(QString displaystring){ui->listWidget->insertItem(0,displaystring);
}void MainWindow::DisplayRecvData(QString displaystring){ui->listWidget_2->insertItem(0,displaystring);
}


好啦!到这里这篇文章就结束啦!这就是本篇文章的全部内容了,接下来我还是会更新一些关于Qt基础编程的相关内容的!记得点点小爱心和关注哟!!!一起共同进步,交流学习!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/310.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【YOLO系列PR、F1绘图】更改v5、v7、v8(附v8训练、验证方式),实现调用val.py或者test.py后生成pr.csv,然后再整合绘制到一张图上(使用matplotlib绘制)

目录 1. 前提 效果图2. 更改步骤2.1 得到PR_curve.csv和F1_curve.csv2.1.1 YOLOv7的更改2.1.1.1 得到PR_curve.csv2.2.1.2 得到F1_curve.csv 2.1.2 YOLOv5的更改&#xff08;v6.1版本&#xff09;2.1.3 YOLOv8的更改&#xff08;附训练、验证方式&#xff09; 2.2 绘制PR曲线 …

【创建型模式】抽象工厂模式

一、抽象工厂模式概述 抽象工厂模式定义&#xff1a;提供一个创建一系列相关或相互依赖对象的接口&#xff0c;而无须指定它们具体的类。 模式动机&#xff1a; 1.当系统提供的工厂生产的具体产品并不是一个简单的对象&#xff0c;而是多个位于不同产品等级结构、属于不同类型的…

HiveSql中的函数家族(一)

一.内置函数 1-1 日期类型操作 -- 获取当前日期 select current_date(); -- 获取当前日期时间 select current_timestamp(); -- 获取unix时间&#xff08;时间戳&#xff09; 从1970年1月1号0时0分0秒 到现在过去了多少秒 select unix_timestamp();-- unix时间 和日期时间的转…

41、二叉树-二叉树的层序遍历

思路&#xff1a; 层序遍历就是从左到右依次遍历。这个时候就可以使用队列的方式。例如先把头节点入队&#xff0c;然后遍历开始&#xff0c;首先计算队列长度&#xff0c;第一层&#xff0c;长度为了&#xff0c;遍历一次&#xff0c;依次出队&#xff0c;头结点出队&#xff…

Tomcat和Spring Boot配置https

生成测试证书 生成证书前&#xff0c;先验证本地是否正确配置jdk环境变量&#xff0c;如果jdk环境变量配置正确&#xff0c;在命令行程序输入生成证书的命令。 keytool -genkey -alias tomcat -keyalg RSA -keystore "F:\job\apache-tomcat-8.5.29\key\freeHttps.keysto…

微信小程序之图片上传并保存在服务器

先将图片上传到服务器&#xff0c;后端接口将保存好的图片地址返回给小程序&#xff0c;再将小程序中添加图像的图片的url替换为服务器中照片的存储地址&#xff08;使微信小程序中显示出上传的图片&#xff09;。 1、效果如下&#xff1a; 点击图像后选择图像&#xff1a; 结…

Kafka不仅是消息队列而是一个分布式消息处理平台

目录 1. kafka架构图 2.关键概念解析 2.1 producer 2.2 consumer: 2.3 brkoer 2.4 Topic 与 Partition 2.5 AR (Assigned Replicas) 2.6 ISR(In-Sync Replicas) 2.7 OSR (Out-of-Sync Replicas) 2.8 HW (High Water-mark) 2.9 LEO (Log End Offset)

搜维尔科技:【工业仿真】煤矿机械安全事故VR警示教育系统

产品概述 搜维尔科技 煤矿机械安全事故VR警示教育系统 系统内容&#xff1a; 系统采用虚拟现实技术模拟矿井井下机械安全技术及事故&#xff0c;展现井下常见机械伤害事故&#xff0c;表现伤害事故的隐患点&#xff0c;能够模拟事故发生和发展过程&#xff1b;营造井下灾害发…

如何使用 Node.js 发送电子邮件全解和相关工具推荐

大多数Web应用程序都需要发送电子邮件。它可能用于注册、密码重置、状态报告&#xff0c;甚至是完整的市场营销活动&#xff0c;如新闻和促销。本教程解释了如何在Node.js中发送电子邮件&#xff0c;但其概念和挑战适用于您正在使用的任何系统。 你会在 npm 上找到大量与电子邮…

详细UI色彩搭配方案分享

UI 配色是设计一个成功的用户界面的关键之一。UI 配色需要考虑品牌标志、用户感受、应用程序的使用场景&#xff0c;这样可以帮助你创建一个有吸引力、易于使用的应用程序。本文将分享 UI 配色的相关知识&#xff0c;帮助设计师快速构建 UI 配色方案&#xff0c;以满足企业的需…

windows10小皮安装不同版本composer,实现自由切换使用

1、使用phpstudy小皮面板安装composer1.8.5和composer2.5.8两个版本&#xff1b; 2、打开刚才安装的composer安装目录&#xff1a;D:\phpstudy_pro\Extensions 3、打开composer1.8.5版本&#xff0c;修改composer.bat名称为composer1.8.5.bat&#xff1a; 4、打开composer2.5.8…

隐私计算DataTrust:从产品需求到工程架构实践

继上期介绍了新监管形势下的隐私技术及数据共享合规设计的思考,本期将接着为大家讲解,国内唯一一个获得工信部三项隐私计算测评的产品DataTrust,在隐私计算领域从产品需求到工程架构的实践之路。 随着数据作为第五大生产要素被提出,“数据流通”的社会价值已形成广泛共识,…

Linux命令学习—Apache 服务器(下)

1.7、访问控制、认证授权的综合指令 1.7.1、两种综合情况 1、满足一种条件即可访问 Satisfy any 或者满足访问控制的条件&#xff0c;或者满足认证授权的条件&#xff0c;就可以访问指定页面、目录 2、必须同时满足 2 个条件才能访问 Satisfy all必须同时满足访问控制和认…

vue的实现八股

双向绑定原理 Vue的双向绑定原理是通过数据劫持和观察者模式实现的。 vue使用了响应式的对象&#xff0c;即当数据发生改变的时候&#xff0c;视图也会随之改变 数据劫持&#xff1a; vue2使用了object.definedproperty对数据的每个属性进行劫持&#xff0c;从而逐一对每个…

【报错】Process finished with exit code 139 (interrupted by signal 11: SIGSEGV)

在你检查完没有内存溢出等各种各种情况之后&#xff0c;仍然不知道该怎么解决&#xff0c;这里提供一个可能的解决办法。 如果你也用的是Mac M1芯片&#xff0c;在跑numpy的时候出现 Intel MKL Warning&#xff1b; 或在用pytorch训练模型的时候遇到segmentation fault。有可能…

【机器学习300问】75、如何理解深度学习中Dropout正则化技术?

一、Dropout正则化的原理是什么&#xff1f; Dropout&#xff08;随机失活&#xff09;正则化是一种用于减少神经网络中过拟合现象的技术。Dropout正则化的做法是&#xff1a; 在训练过程中的每次迭代中&#xff0c;随机将网络中的一部分权重临时"丢弃"&#xff08;即…

Java工具类:压缩图片至指定大小

不好用请移至评论区揍我 原创代码,请勿转载,谢谢! 一、介绍 接收File参数及目标大小,将自动递归压缩至指定大小已增加删除压缩产生的临时文件等逻辑处理传递的原文件将不会执行delete操作,而是在基础上返回压缩后的文件传递文件名示例(xxx.txt),压缩后文件名示例(xxx_…

前端三大件速成 01 HTML

文章目录 一、前端基础知识二、标签1、什么是标签2、标签的属性3、常用标签&#xff08;1&#xff09;声明&#xff08;2&#xff09;注释&#xff08;3&#xff09;html 根标签&#xff08;3&#xff09;head标签&#xff08;4&#xff09;body标签 三、特殊字符四、其他标签1…

web安全学习笔记(11)

记一下第十五节课的内容。 一、创建MySQL执行函数 我们在function.php中&#xff0c;自定义一个函数&#xff1a; #SQL查询函数 function Qurey($sql) {#连接数据库$db new mysqli(172.20.10.3, liuyan, 123456, liuyan, 3306);#判断是否连接成功if (mysqli_connect_errno(…

redis的数据结构报错

文章目录 redis的数据结构报错Redis使用LocalDateTime报错问题 redis的数据结构报错 Redis使用LocalDateTime报错问题 SpringBoot整合Redis时&#xff0c;使用LocalDate以下报错 org.springframework.data.redis.serializer.SerializationException: Could not read JSON: C…