Qt简单编程实现UDP通讯
作者:三号原子
UDP通讯
UDP数据报协议是一个面向无连接的传输层报文协议,它简单易用,不存在 TCP协议“粘包”的问题,在强调实时、主动推送的系统中,常常用 UDP协议来实现网络双方的通信。在 Qt 中,QUdpSocket 类提供了 UDP 数据报的通信支持,下面通过两个简单的例子介绍Qt下 UDP 协议的实现。
模拟网络上经常定义的数据报文结构:
字节 | 1~4 | 5~8 | 9~12 | 13~16 | 17~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简单编程实现UDP通讯的文章就介绍到这了,更多相关Qt UDP通讯内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!