基于窗口部件的UDP通信实现,虽然简单易用,但是窗口部件主要的工作是负责处理大量的用户界面信息,当有耗时的处理过程时,会影响数据的接收,造成丢帧。这里使用的是独立的线程负责网络数据的发送和接收,再通过窗口部件显示出来,在实时系统中应用特别广泛。下面是基于线程实现UDP通信的列程。
新建工程,在工程中新建发送和接收的C++类,sendthread.h,sendthread.cpp,recvthread.h,recvthread.cpp。
(1)sendthread.h定义如下:
#ifndef SENDTHREAD_H
#define SENDTHREAD_H
#include <QThread>
#include <QtNetwork/QNetworkInterface>
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QUdpSocket>
/*添加网络数据报文的结构定义*/
#pragma pack(push) //保存对齐状态
#pragma pack(2) //设定为4字节对齐
struct DataStruct{
unsigned short frame;
unsigned short index;
unsigned short year;
unsigned short month;
unsigned short day;
unsigned short end;
};
//NetBuffer联合提供了一种访问与DataStruct结构大小为12的字符数组相同的内存空间的方法
union NetBuffer{
DataStruct data;
char dataBuffer[12];
};
#pragma pack(pop) //恢复字节对齐状态
/******************************************************************************************/
/******************************************************************************************/
class SendThread : public QThread
{
Q_OBJECT
public:
explicit SendThread(QWidget * parent=0);
protected:
void run();//定义线程需要重载的run()操作
private:
/*定义线程需要用到的主机地址hostAddress,UDPsocke端口和发送缓冲区NetBuffer*/
QHostAddress hostAddress;
QUdpSocket udpSendSocket;
NetBuffer sendBuffer;
};
#endif // SENDTHREAD_H
(2)sendthread.cpp定义如下:
#include "sendthread.h"
#include "mainwindow.h"
#include <QTime>
#include <QList>
/*初始化参数,获取本机地址,绑定socket端口*/
SendThread::SendThread(QWidget * parent):
QThread(parent)
{
QList<QHostAddress>addressList=QNetworkInterface::allAddresses();
hostAddress=addressList.at(0);
udpSendSocket.bind(hostAddress,3407);
sendBuffer.data.index=0;
}
/*重载实现run()操作*/
void SendThread::run()
{
while(true) {
//QTime tm = QTime::currentTime();
sendBuffer.data.frame = 0x0ff0;
sendBuffer.data.index++;
sendBuffer.data.year = 2023;
sendBuffer.data.month = 9;
sendBuffer.data.day = 1;
sendBuffer.data.end = 0x0ee0;
udpSendSocket.writeDatagram(sendBuffer.dataBuffer,sizeof(sendBuffer),hostAddress,3408);
QString displayString;
displayString = QString("帧头 = %1 \n 帧计数 = %2 \n 年 = %3 \n 月 = %4 \n 日 = %5 \n 帧尾 = %6 \n")
/*第一个参数是要输出的值包括字符串和各种基本类型的值,第二个参数是字段宽度,
* 第三个参数一般是用2、8、10、16表示进制,最后一个参数是填充字符
*/
.arg(sendBuffer.data.frame,2,16)
.arg(sendBuffer.data.index,2,10)
.arg(sendBuffer.data.year,2,10)
.arg(sendBuffer.data.month,2,10)
.arg(sendBuffer.data.day,2,10)
.arg(sendBuffer.data.end,2,16);
((MainWindow*)this->parent())->DisplaySendData(displayString);
this->sleep(1);
}
}
(3) recvthread.h定义如下:
#ifndef RECVTHREAD_H
#define RECVTHREAD_H
#include "sendthread.h"
//UDP通信需要定义的头文件
#include <QThread>
#include <QtNetwork/QNetworkInterface>
#include <QtNetwork/QHostAddress>
#include <QtNetwork/QUdpSocket>
class RecvThread:public QThread
{
Q_OBJECT
public:
explicit RecvThread(QWidget * parent=0);
private:
/*定义线程需要用到的主机地址hostAddress,UDPsocke端口和发送缓冲区NetBuffer*/
QHostAddress hostAddress;
QUdpSocket udpRecvSocket;
NetBuffer recvBuffer;
protected:
void run();//定义线程需要重载的run()操作
};
#endif // RECVTHREAD_H
(4) recvthread.cpp定义如下:
#include "recvthread.h"
#include "mainwindow.h"
#include <QList>
#include <QDebug>
/*初始化参数,获取本机地址,绑定socket端口*/
RecvThread::RecvThread(QWidget * parent):QThread(parent)
{
QList<QHostAddress>addressList=QNetworkInterface::allAddresses();
hostAddress=addressList.at(0);
udpRecvSocket.bind(hostAddress,3408);
}
/*
* 在run()中读取网络数据,并通过主窗口的DisplayRecvData方式显示
* 这里使用了waiForReadyRead方法,以同步的方式读取数据,而不是使用信号和槽的异步方法
* 当没有新数据到来时,线程处于挂起等待状态,当有数据到达时,立刻进入下一步处理,这种方式响应的更及时快速
*/
void RecvThread::run()
{
while(true)
{
if(udpRecvSocket.waitForReadyRead())
{
QHostAddress sender;
quint16 senderPort;
udpRecvSocket.readDatagram(recvBuffer.dataBuffer,sizeof(recvBuffer),&sender,&senderPort);
QString displayString;
displayString = QString("帧头 = %1 \n 帧计数 = %2 \n 年 = %3 \n 月 = %4 \n 日 = %5 \n 帧尾 = %6 \n")
.arg(recvBuffer.data.frame,2,16)
.arg(recvBuffer.data.index,2,10)
.arg(recvBuffer.data.year,2,10)
.arg(recvBuffer.data.month,2,10)
.arg(recvBuffer.data.day,2,10)
.arg(recvBuffer.data.end,2,16);
((MainWindow*)this->parent())->DisplayRecvData(displayString);
qDebug("打印输出数据");
}
}
}
(5)mainwindow.h定义如下:
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
void DisplayRecvData(QString displayString);
void DisplaySendData(QString displayString);
private:
Ui::MainWindow *ui;
};
#endif // MAINWINDOW_H
(6)mainwindow.cpp定义如下:
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "sendthread.h"
#include "recvthread.h"
/*在主窗口中,发送和接收socket线程,定义DisplaySendData和DisplayRecvData操作显示收发数据*/
MainWindow::MainWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::MainWindow)
{
ui->setupUi(this);
SendThread *sendThread = new SendThread(this);
RecvThread *recvThread = new RecvThread(this);
recvThread->start();
sendThread->start();
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::DisplaySendData(QString displayString)
{
ui->listWidget->insertItem(0,displayString);
}
void MainWindow::DisplayRecvData(QString displayString)
{
ui->listWidget_2->insertItem(0,displayString);
}
(7)运行结果如图所示:
(8)源码地址:QT实现基于线程的UDP通信