[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_OBJECT

public:
    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基础编程的相关内容的!记得点点小爱心和关注哟!!!一起共同进步,交流学习!

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【opencv】dnn示例-segmentation.cpp 通过深度学习模型对图像进行实时语义分割

模型下载地址&#xff1a; http://dl.caffe.berkeleyvision.org/ 配置文件下载&#xff1a; https://github.com/opencv/opencv_extra/tree/4.x/testdata/dnn 该段代码是一个利用深度学习进行语义分割的OpenCV应用实例。下面将详细解释代码的功能和方法。 引入库 引入了一些必要…

统一SQL-支持cast函数

统一SQL介绍 https://www.light-pg.com/docs/LTSQL/current/index.html 源和目标 源数据库&#xff1a;Oracle 目标数据库&#xff1a;Postgresql&#xff0c;TDSQL-MySQL&#xff0c;达梦8&#xff0c;LightDB-Oracle 操作目标 在Oracle中&#xff0c;cast函数允许将一种…

代码随想录-链表 | 142环形链表

代码随想录-链表 | 142环形链表 LeetCode 142-环形链表解题思路代码复杂度难点总结 LeetCode 142-环形链表 题目链接 题目描述 给定一个链表的头节点 head &#xff0c;返回链表开始入环的第一个节点。 如果链表无环&#xff0c;则返回 null。 如果链表中有某个节点&#x…

死磕GMSSL通信-C/C++系列(一)

死磕GMSSL通信-C/C++系列(一) 最近再做国密通信的项目开发,以为国密也就简单的集成一个库就可以完事了,没想到能有这么多坑。遂写下文章,避免重复踩坑。以下国密通信的坑有以下场景 1、使用GMSSL guanzhi/GmSSL进行通信 2、使用加密套件SM2-WITH-SMS4-SM3 使用心得 ​…

Java学习-详述main方法、可变参数、数组的工具类、二维数组

详述main方法 【1】main方法&#xff1a;程序的入口&#xff0c;在同一个类中&#xff0c;如果有多个方法&#xff0c;那么虚拟机就会识别main方法&#xff0c;从这个方法作为程序的入口 【2】main方法格式严格要求&#xff1a; public static void main(String[] args){} p…

【NOI-题解】1320. 时钟旋转1323. 扩建花圃问题1462. 小明的游泳时间1565. 成绩(score)1345. 玫瑰花圃

文章目录 一、前言二、问题问题&#xff1a;1320. 时钟旋转问题&#xff1a;1323. 扩建花圃问题问题&#xff1a;1462. 小明的游泳时间问题&#xff1a;1565. 成绩&#xff08;score&#xff09;问题&#xff1a;1345. 玫瑰花圃 三、感谢 一、前言 本章节主要对基本运算中整数…

Windows系统搭建Plex网站结合内网穿透实现公网访问本地影音文件

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 用手机或者平板电脑看视频&#xff0c;已经算是生活中稀松平常的场景了&#xff0c;特别是各…

奶酪——并查集,BFS,DFS(NOIP2017提高组)

目录 题目 思路 并查集 代码&#xff08;java&#xff09; BFS&#xff08;DFS同理&#xff09; 代码&#xff08;C&#xff09; 题目 思路 这个题目意思是有很多个球分布在一个三维空间内&#xff0c;如果这些球相切或者相交都可以互相到达&#xff0c;我们需要判断能否…

二、e2studio VS STM32CubeIDE之功能对比

目录 一、概述/目的 二、官网资料 2.1 stm32cubeide 2.2 e2studio 三、功能对比 二、e2studio VS STM32CubeIDE之功能对比 一、概述/目的 通过对比学习&#xff0c;更快速的掌握两款IDE 二、官网资料 2.1 stm32cubeide https://www.stmcu.com.cn/ecosystem/Cube/STM32C…

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

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

Codeforces Round 924 (Div. 2) ---- F. Digital Patterns ---- 题解

F. Digital Patterns&#xff1a; 题目描述&#xff1a; 思路解析&#xff1a; 要求在一个方块中&#xff0c;任意相邻的方块中他的透明度系数不能相同&#xff0c;这样的方块称为趣味性方块&#xff0c;问这样的方块有多少种。 那么我们可以相当&#xff0c;假设 a1 a2, 那…

iOS ------ Block的总结

前面看了Block的基本知识&#xff0c;和一些源码。但对于block怎么用的还不了解&#xff0c;代码中出现block会看不懂&#xff0c;现在来具体看一下Block的用法并做个总结。 1.Block是什么 block对象是一个C语言结构体&#xff0c;可以并入C和OC的代码中&#xff0c;Block本质…

每日OJ题_多源BFS①_力扣542. 01 矩阵(多源BFS解决最短路原理)

目录 多源BFS解决最短路算法原理 力扣542. 01 矩阵 解析代码 多源BFS解决最短路算法原理 什么是单源最短路 / 多源最短路&#xff1f; 之前的BFS解决最短路都是解决的单源最短路。 画图来说&#xff0c;单源最短路问题即为&#xff1a; 而对于多源最短路问题: 如何解决此…

微信小程序之点击事件

微信小程序中常用的点击事件主要是 tap&#xff0c;但除此之外还有其他的触摸类事件&#xff0c;用于不同的交互场景。以下是一些常见的点击和触摸相关的事件及其区别&#xff1a; 1、tap——最基本的点击事件&#xff0c;适用于一般的轻触交互&#xff0c;类似于 HTML 中的 c…

Pixverse:开启文生视频与图生视频新纪元

✨✨ 欢迎大家来访Srlua的博文&#xff08;づ&#xffe3;3&#xffe3;&#xff09;づ╭❤&#xff5e;✨✨ &#x1f31f;&#x1f31f; 欢迎各位亲爱的读者&#xff0c;感谢你们抽出宝贵的时间来阅读我的文章。 我是Srlua小谢&#xff0c;在这里我会分享我的知识和经验。&am…

NPU流式输出-torch_npu和transformers框架-多线程Streamer-昇腾910B-EE1001

前情提要 torch_npu框架不支持多线程自动set_device 报错详情 直接使用transformers的TextIteratorStreamer进行流式推理&#xff0c;会报错 Exception in thread Thread-6: Traceback (most recent call last):File "/root/anaconda3/envs/AI/lib/python3.9/threadin…

Linux shell 脚本基础与部署SpringCloud实战

博主介绍&#xff1a;✌全网粉丝5W&#xff0c;全栈开发工程师&#xff0c;从事多年软件开发&#xff0c;在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战&#xff0c;博主也曾写过优秀论文&#xff0c;查重率极低&#xff0c;在这方面有丰富的经验…

L2正则化——解释为什么可以减少模型的复杂度

L2正则化是一种用于机器学习模型的技术&#xff0c;旨在减少模型的复杂度&#xff0c;从而提高其泛化能力。在L2正则化中&#xff0c;通过添加一个惩罚项&#xff0c;模型的权重被迫保持较小的值&#xff0c;这有助于防止过拟合&#xff0c;即模型在训练数据上表现良好但在未见…

python学习笔记B-07:序列结构之列表--列表的常用函数和方法

以xx_函数名(列表名)的形式出现的是函数&#xff1b;以xx_列表名.xx_方法名的形式出现的是方法。 列表常用函数如下&#xff1a; len()&#xff1a;计算列表元素数量 max()&#xff1a;获取列表元素最大值 min():获取列表元素最小值 sum():计算列表中各元素之和 列表常用方法如…

【Java EE】文件操作

目录 1.认识文件 2.树型结构组织和目录 3.文件路径&#xff08;Path&#xff09; 4.其他知识 5.Java中操作文件 5.1File概述 5.1.1属性 5.1.2构造方法 5.1.3方法 5.2代码示例 1.认识文件 我们先来认识狭义的文件&#xff08;file&#xff09;。针对1硬盘这种持久化存…