Qt簡(jiǎn)單編程實(shí)現(xiàn)UDP通訊
UDP通訊
UDP數(shù)據(jù)報(bào)協(xié)議是一個(gè)面向無(wú)連接的傳輸層報(bào)文協(xié)議,它簡(jiǎn)單易用,不存在 TCP協(xié)議“粘包”的問(wèn)題,在強(qiáng)調(diào)實(shí)時(shí)、主動(dòng)推送的系統(tǒng)中,常常用 UDP協(xié)議來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)雙方的通信。在 Qt 中,QUdpSocket 類提供了 UDP 數(shù)據(jù)報(bào)的通信支持,下面通過(guò)兩個(gè)簡(jiǎn)單的例子介紹Qt下 UDP 協(xié)議的實(shí)現(xiàn)。
模擬網(wǎng)絡(luò)上經(jīng)常定義的數(shù)據(jù)報(bào)文結(jié)構(gòu):
字節(jié) | 1~4 | 5~8 | 9~12 | 13~16 | 17~20 |
定義 | 序號(hào) | 小時(shí) | 分鐘 | 秒 | 毫秒 |
#pragma pack(push) //保存對(duì)齊狀態(tài) #pragma pack(4) //設(shè)定為4字節(jié)對(duì)齊 struct DataStruct{ unsigned int index;//序號(hào) int hour;//小時(shí) int minute;//分鐘 int second;//秒 int msec;//毫秒 }; union NetBuffer{ DataStruct data; char dataBuffer[20]; }; #pragma pack(pop) //恢復(fù)對(duì)齊狀態(tài)
這里用了一個(gè)聯(lián)合定義的數(shù)據(jù)緩沖區(qū),便于進(jìn)行數(shù)據(jù)報(bào)文的設(shè)置和解析。
需要在 *.pro 工程文件中添加 network 選項(xiàng) :
QT +=core gui network
基于主窗口的實(shí)現(xiàn)
UDP報(bào)文的發(fā)送比較隨意,可以在程序的任何需要的時(shí)候和位置發(fā)送 UDP報(bào)文,為了演示的簡(jiǎn)單,本例子中設(shè)置了主窗口的定時(shí)器,每秒鐘發(fā)送一次報(bào)文。在接收的時(shí)候,響應(yīng)接收端口 readyRead()信號(hào),及時(shí)讀取網(wǎng)絡(luò)協(xié)議緩沖區(qū)的數(shù)值。
1.新建一個(gè)工程,在界面中添加兩個(gè)列表部件,用于顯示發(fā)送和接收的數(shù)據(jù):
2. 在頭文件中,添加包含 QNetworkInterface、QHostAddress 和 QudpSocket 模塊,添加網(wǎng)絡(luò)數(shù)據(jù)報(bào)文的結(jié)構(gòu)定義。在 MainWindow 類定義中,添加需要重載的 timerEvent 定義,添加讀取數(shù)據(jù)報(bào)文操作 readPendingDatagrams 定義,以及主機(jī)地址、發(fā)送和接收 socket和緩沖區(qū)定義。
#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) //保存對(duì)齊狀態(tài) #pragma pack(4) //設(shè)定為4字節(jié)對(duì)齊 struct DataStruct{ unsigned int index;//序號(hào) int hour;//小時(shí) int minute;//分鐘 int second;//秒 int msec;//毫秒 }; union NetBuffer{ DataStruct data; char dataBuffer[20]; }; #pragma pack(pop) //恢復(fù)對(duì)齊狀態(tài) 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 的構(gòu)造函數(shù)中,獲取本機(jī)地址,綁定發(fā)送和接收 socket,設(shè)置響應(yīng)接收 socket 接收信號(hào)的槽。
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent) , ui(new Ui::MainWindow) { ui->setupUi(this); //通過(guò)調(diào)用靜態(tài)方法獲取本機(jī)IP地址 QList<QHostAddress> addressList = QNetworkInterface::allAddresses(); hostAddress=addressList.at(0); //網(wǎng)絡(luò)端口綁定 udpSendSocket.bind(hostAddress,7000); udpRecvSocket.bind(hostAddress,7001); //設(shè)置定時(shí)器 this->startTimer(1000); //初始化發(fā)送計(jì)數(shù)器 sendBuffer.data.index=0; //建立接收 socket 的連接 QObject::connect(&udpRecvSocket,SIGNAL(readyRead()),this,SLOT(readPendingDatagrams())); }
4.實(shí)現(xiàn)發(fā)送和接收操作,并在列表中顯示。發(fā)送操作是在定時(shí)器事件響應(yīng)函數(shù)中實(shí)現(xiàn)的,上面已經(jīng)設(shè)置了每秒發(fā)送一次。數(shù)據(jù)接收是在 readPendingDatagrams()函數(shù)中實(shí)現(xiàn)的,當(dāng)接收 socket 一有數(shù)據(jù)報(bào)文包,readPendingDatagrams()就被調(diào)用,讀取網(wǎng)絡(luò)接收到的數(shù)據(jù),并解析顯示。在這里我們用了聯(lián)合的方法來(lái)解析網(wǎng)絡(luò)數(shù)據(jù)結(jié)構(gòu),方便易用。
//在 timeEvent 中設(shè)置發(fā)送數(shù)據(jù),并在列表中顯示 void MainWindow::timerEvent(QTimerEvent * event){ QTime tm = QTime::currentTime();//獲取當(dāng)前時(shí)間 sendBuffer.data.hour=tm.hour(); sendBuffer.data.minute=tm.minute(); sendBuffer.data.second=tm.second(); sendBuffer.data.msec=tm.msec(); //調(diào)用發(fā)送數(shù)據(jù)包函數(shù),發(fā)送數(shù)據(jù) 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 槽中,接收數(shù)據(jù)并顯示 void MainWindow::readPendingDatagrams (){ QHostAddress sender; quint16 senderPort; //調(diào)用數(shù)據(jù)接接收函數(shù),接收數(shù)據(jù) 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); }
基于線程的實(shí)現(xiàn)
基于窗口部件的 UDP通信實(shí)現(xiàn),雖然簡(jiǎn)單易用,但是窗口部件主要的工作是負(fù)責(zé)處理大量的用戶界面信息,當(dāng)有耗時(shí)的處理過(guò)程時(shí),會(huì)影響數(shù)據(jù)的接收,造成丟幀。通常的做法是用獨(dú)立的線程負(fù)責(zé)網(wǎng)絡(luò)數(shù)據(jù)的發(fā)送和接收,再通過(guò)窗口部件顯示輸出,在實(shí)時(shí)系統(tǒng)中這種應(yīng)用特別廣泛。下面的例子顯示的效果和前面一致,但實(shí)現(xiàn)的機(jī)理是完全不同的。
1.新建工程,在工程中依次新建發(fā)送和接收線程的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" //就是上文定義的數(shù)據(jù)緩沖 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中定義了線程需要用到的主機(jī)地址 hostAddress、UDPsocket 端口和發(fā)送緩沖區(qū),定義了線程需要重載的 run()操作。在 sendthread.cpp 的構(gòu)造函數(shù)中,初始化參數(shù),獲取本機(jī)地址,綁定 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; }
然后重載實(shí)現(xiàn) run()操作。這里要注意的是,由于主窗口的 ui變量是 protected 類型線程不能直接使用,需要線程通過(guò)主窗口的 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; };
和發(fā)送線程類似,定義了主機(jī)地址 hostAddress、UDPsocket 端口和發(fā)送緩沖區(qū),定義了需要重載的 run()操作。在構(gòu)造函數(shù)中,初始化接收 socket。
recvthread::recvthread(QWidget *parent): QThread(parent) { QList<QHostAddress> addresslist=QNetworkInterface::allAddresses(); hostAddress=addresslist.at(0); udpRecvSocket.bind(hostAddress,7001); }
在 run()中讀取網(wǎng)絡(luò)數(shù)據(jù),并通過(guò)主窗口的 DisplayRecvData方法顯示。注意這里使用了 waitForReadyRead方法以同步方式讀取數(shù)據(jù),而不是使用信號(hào)和槽的異步方法。當(dāng)沒有新數(shù)據(jù)到來(lái)時(shí),線程處于掛起等待狀態(tài),當(dāng)有數(shù)據(jù)到達(dá)時(shí),立刻進(jìn)入下一步處理,這種方法響應(yīng)得更及時(shí)快速。
#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.在主窗口中,初始化發(fā)送和接收 socket 線程,定義 DisplaySendData 和 DisplayRecvData操作顯示收發(fā)數(shù)據(jù)。
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); }
到此這篇關(guān)于Qt簡(jiǎn)單編程實(shí)現(xiàn)UDP通訊的文章就介紹到這了,更多相關(guān)Qt UDP通訊內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Qt利用tablewidget模擬手指實(shí)現(xiàn)滑動(dòng)
這篇文章主要為大家詳細(xì)介紹了Qt如何利用tablewidget模擬手指實(shí)現(xiàn)滑動(dòng)效果,文中的示例代碼講解詳細(xì),對(duì)我們學(xué)習(xí)Qt有一定的幫助,需要的可以參考一下2023-01-01C/C++ 中const關(guān)鍵字的用法小結(jié)
C++中的const關(guān)鍵字的用法非常靈活,而使用const將大大改善程序的健壯性。這篇文章主要介紹了C/C++ 中const關(guān)鍵字的用法,需要的朋友可以參考下2020-02-02VC++實(shí)現(xiàn)文件與應(yīng)用程序關(guān)聯(lián)的方法(注冊(cè)表修改)
這篇文章主要介紹了VC++實(shí)現(xiàn)文件與應(yīng)用程序關(guān)聯(lián)的方法,涉及VC++針對(duì)注冊(cè)表的相關(guān)操作技巧,需要的朋友可以參考下2016-08-08C++函數(shù)指針與指針函數(shù)有哪些關(guān)系和區(qū)別
函數(shù)指針是一個(gè)指針變量,它可以存儲(chǔ)函數(shù)的地址,然后使用函數(shù)指針,這篇文章主要介紹了C++中函數(shù)指針與指針函數(shù)有哪些關(guān)系和區(qū)別,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值2022-08-08