[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.hqwc.cn/news/630742.html

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

相关文章

【最新可用】Claude国内镜像,可上传图片,可用Claude3全系模型,包括Pro版本的Opus),亲测比GPT好用

Claude对话、上传图片的超详细教程来啦&#xff01; 近期&#xff0c;Claude 3 Opus的发布引发了网络上的广泛关注与热议&#xff0c;有观点认为其性能已经凌驾于GPT-4之上。虽然网络上已经出现了大量基于这两款先进AI技术的实际应用案例&#xff0c;但仍有许多人对在国内如何…

【CANoe】节点/报文DLC检测的CAPL实现

文章目录 一、CAPL中节点/报文DLC检测的相关函数二、CAPL脚本实现三、实测效果1、成功报告2、失败报告(可通过改dbc文件中报文的DLC来触发失败)一、CAPL中节点/报文DLC检测的相关函数 在CAPL中有如下三个函数,可以检测节点/报文的DLC。 第一个检测报文的DLC第二、三个检测节…

C++进修——C++基础入门

初识C 书写HelloWorld #include <iostream> using namespace std;int main() {cout << "HelloWorldd" << endl;system("pause");return 0; }注释 作用&#xff1a;在代码中加一些说明和解释&#xff0c;方便自己或其他程序员阅读代码…

算法一:数字 - 两数之和

给定一个整数数组 nums 和一个目标值 target&#xff0c;请你在该数组中找出和为目标值的那 两个 整数&#xff0c;并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是&#xff0c;数组中同一个元素不能使用两遍。 来源&#xff1a;力扣(LeetCode) 链接&#xf…

Python中的模块化编程与软件架构设计【第171篇—软件架构设计】

&#x1f47d;发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【点击进入巨牛的人工智能学习网站】。 Python中的模块化编程与软件架构设计 在软件开发中&#xff0c;模块化编程和良好的软件架构…

Visual Studio调试C/C++指南

1. 前言 Visual Studio&#xff08;VS&#xff09;是微软开发的一款集成开发环境(IDE)软件&#xff0c;支持C/C、C#、VB、Python等开发语言&#xff0c;开发桌面、Web等应用程序。VS功能极其强大&#xff0c;使用极其便利&#xff0c;用户数量最多&#xff0c;被誉为"宇宙…

鸿蒙入门06-常见装饰器( 简单装饰器 )

装饰器是鸿蒙开发中非常重要的一个环节因为在很多地方我们都需要用到装饰器并且如果我们想高度的复用, 那么装饰器就是必不可少的一环接下来我们就来介绍一些常见的装饰器注意 : 所有装饰器首字母大写 Entry 用来装饰 struct 使用表示页面的入口 Component 装饰 struct, …

芜湖等保测评机构有哪些?在哪里?

芜湖等保测评机构有哪些&#xff1f;在哪里&#xff1f; 【回答】&#xff1a;目前芜湖没有具有正规资质的等保测评机构。芜湖企业可以就近选择安徽省内正规等保测评机构&#xff0c;也可以网上选择。安徽省内等保测评机构看这里&#xff1a;https://www.cloudbility.com/club…

ASUS华硕ROG幻13笔记本电脑GV301R工厂模式原厂OEM预装Windows11系统,恢复出厂开箱状态

适用于型号&#xff1a;GV301RC、GV301RE、GV301RA 工厂模式安装包&#xff1a;https://pan.baidu.com/s/1gLme1VqidpUjCLocgm5ajQ?pwddnbk 提取码&#xff1a;dnbk 工厂模式Win11安装包带有ASUS RECOVERY恢复功能、自带所有驱动、出厂主题壁纸、系统属性专属联机支持标志…

OWASP发布大语言模型网络安全与治理清单

当前人工智能技术面临的最大风险是大语言模型&#xff08;LLM&#xff09;和生成式人工智能技术的发展和应用速度已经远远超过了安全和治理的速度。 OpenAI、Anthropic、谷歌和微软等公司的生成式人工智能和大语言模型产品的使用正呈指数级增长。与此同时&#xff0c;开源大语…

Leetcode 28. 找出字符串中第一个匹配项的下标

心路历程&#xff1a; 两个字符串匹配的问题基本都可以用动态规划解决&#xff0c;递推关系就是依次匹配下去 注意的点&#xff1a; 1、注意边界条件是匹配串needle到头&#xff0c;但是haystack不一定需要到头 2、这道题按照从i开始的字符串而不是从i结束的进行DP建模 解法…

【SAP HANA 15】SQL锁表 (查询,解锁)

锁表查看 --锁表检查语句 SELECT C.CONNECTION_ID,PS.STATEMENT_STRINGFROM M_CONNECTIONS C JOIN M_PREPARED_STATEMENTS PSON C.CONNECTION_ID PS.CONNECTION_ID AND C.CURRENT_STATEMENT_ID PS.STATEMENT_IDWHERE C.CONNECTION_STATUS RUNNINGAND C.CONNECTION_TYPE Re…