当前位置:   article > 正文

[Qt网络编程]之UDP通讯的简单编程实现

[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
定义序号小时分钟毫秒
  1. #pragma pack(push) //保存对齐状态
  2. #pragma pack(4) //设定为4字节对齐
  3. struct DataStruct{
  4. unsigned int index;//序号
  5. int hour;//小时
  6. int minute;//分钟
  7. int second;//秒
  8. int msec;//毫秒
  9. };
  10. union NetBuffer{
  11. DataStruct data;
  12. char dataBuffer[20];
  13. };
  14. #pragma pack(pop) //恢复对齐状态

这里用了一个联合定义的数据缓冲区,便于进行数据报文的设置和解析。

需要在 *.pro 工程文件中添加 network 选项 :

QT +=core gui network

 基于主窗口的实现

        UDP报文的发送比较随意,可以在程序的任何需要的时候和位置发送 UDP报文,为了演示的简单,本例子中设置了主窗口的定时器,每秒钟发送一次报文。在接收的时候,响应接收端口 readyRead()信号,及时读取网络协议缓冲区的数值。

1.新建一个工程,在界面中添加两个列表部件,用于显示发送和接收的数据:

2. 在头文件中,添加包含 QNetworkInterface、QHostAddress 和 QudpSocket 模块,添加网络数据报文的结构定义。在 MainWindow 类定义中,添加需要重载的 timerEvent 定义,添加读取数据报文操作 readPendingDatagrams 定义,以及主机地址、发送和接收 socket和缓冲区定义。

  1. #ifndef MAINWINDOW_H
  2. #define MAINWINDOW_H
  3. #include<QMainWindow>
  4. #include<QtNetwork/QNetworkInterface>
  5. #include<QtNetwork/QHostAddress>
  6. #include<QtNetwork/QUdpSocket>
  7. QT_BEGIN_NAMESPACE
  8. namespace Ui { class MainWindow; }
  9. QT_END_NAMESPACE
  10. #pragma pack(push) //保存对齐状态
  11. #pragma pack(4) //设定为4字节对齐
  12. struct DataStruct{
  13. unsigned int index;//序号
  14. int hour;//小时
  15. int minute;//分钟
  16. int second;//秒
  17. int msec;//毫秒
  18. };
  19. union NetBuffer{
  20. DataStruct data;
  21. char dataBuffer[20];
  22. };
  23. #pragma pack(pop) //恢复对齐状态
  24. class MainWindow : public QMainWindow
  25. {
  26. Q_OBJECT
  27. public:
  28. MainWindow(QWidget *parent = nullptr);
  29. ~MainWindow();
  30. void timerEvent(QTimerEvent * event);
  31. public slots:
  32. void readPendingDatagrams();
  33. private:
  34. Ui::MainWindow *ui;
  35. QHostAddress hostAddress;
  36. QUdpSocket udpSendSocket,udpRecvSocket;
  37. NetBuffer sendBuffer,recvBuffer;
  38. };
  39. #endif // MAINWINDOW_H

3. 在 MainWindow 的构造函数中,获取本机地址,绑定发送和接收 socket,设置响应接收 socket 接收信号的槽。

  1. MainWindow::MainWindow(QWidget *parent)
  2. : QMainWindow(parent)
  3. , ui(new Ui::MainWindow)
  4. {
  5. ui->setupUi(this);
  6. //通过调用静态方法获取本机IP地址
  7. QList<QHostAddress> addressList = QNetworkInterface::allAddresses();
  8. hostAddress=addressList.at(0);
  9. //网络端口绑定
  10. udpSendSocket.bind(hostAddress,7000);
  11. udpRecvSocket.bind(hostAddress,7001);
  12. //设置定时器
  13. this->startTimer(1000);
  14. //初始化发送计数器
  15. sendBuffer.data.index=0;
  16. //建立接收 socket 的连接
  17. QObject::connect(&udpRecvSocket,SIGNAL(readyRead()),this,SLOT(readPendingDatagrams()));
  18. }

4.实现发送和接收操作,并在列表中显示。发送操作是在定时器事件响应函数中实现的,上面已经设置了每秒发送一次。数据接收是在 readPendingDatagrams()函数中实现的,当接收 socket 一有数据报文包,readPendingDatagrams()就被调用,读取网络接收到的数据,并解析显示。在这里我们用了联合的方法来解析网络数据结构,方便易用。

  1. //在 timeEvent 中设置发送数据,并在列表中显示
  2. void MainWindow::timerEvent(QTimerEvent * event){
  3. QTime tm = QTime::currentTime();//获取当前时间
  4. sendBuffer.data.hour=tm.hour();
  5. sendBuffer.data.minute=tm.minute();
  6. sendBuffer.data.second=tm.second();
  7. sendBuffer.data.msec=tm.msec();
  8. //调用发送数据包函数,发送数据
  9. udpSendSocket.writeDatagram (sendBuffer.dataBuffer,sizeof(sendBuffer),hostAddress,7001);
  10. QString displaystring;
  11. displaystring=QString("Index=%1 \nTime=%2:%3:%4.%5\n")
  12. .arg(sendBuffer.data.index)
  13. .arg(sendBuffer.data.hour,2,10,QChar('0'))
  14. .arg(sendBuffer.data.minute,2,10,QChar('0'))
  15. .arg(sendBuffer.data.second,2,10,QChar('0'))
  16. .arg(sendBuffer.data.msec,3,10,QChar('0'));
  17. ui->listWidget->insertItem(0,displaystring);
  18. sendBuffer.data.index++;
  19. }
  20. //在 readPendingDatagrams 槽中,接收数据并显示
  21. void MainWindow::readPendingDatagrams (){
  22. QHostAddress sender;
  23. quint16 senderPort;
  24. //调用数据接接收函数,接收数据
  25. udpRecvSocket.readDatagram(recvBuffer.dataBuffer,sizeof (recvBuffer),&sender,&senderPort);
  26. QString displaystring;
  27. displaystring=QString("Index=%1 \nTime=%2:%3:%4.%5\n").arg (recvBuffer.data.index)
  28. .arg(recvBuffer.data.hour,2,10,QChar('0'))
  29. .arg(recvBuffer.data.minute,2,10,QChar('0'))
  30. .arg(recvBuffer.data.second,2,10,QChar('0'))
  31. .arg(recvBuffer.data.msec,3,10,QChar('0'));
  32. ui->listWidget_2->insertItem(0,displaystring);
  33. }

 


 基于线程的实现

        基于窗口部件的 UDP通信实现,虽然简单易用,但是窗口部件主要的工作是负责处理大量的用户界面信息,当有耗时的处理过程时,会影响数据的接收,造成丢帧。通常的做法是用独立的线程负责网络数据的发送和接收,再通过窗口部件显示输出,在实时系统中这种应用特别广泛。下面的例子显示的效果和前面一致,但实现的机理是完全不同的。

1.新建工程,在工程中依次新建发送和接收线程的C++文件 sendthread. h,sendthread. epp 和 reevthread. h,recvthread. cpp:

其中sendthread.h定义:

  1. #include <QWidget>
  2. #include<QThread>
  3. #include<QtNetwork/QNetworkInterface>
  4. #include<QtNetwork/QHostAddress>
  5. #include<QtNetwork/QUdpSocket>
  6. #include "NetBuffer.h" //就是上文定义的数据缓冲
  7. class sendthread :public QThread
  8. {
  9. Q_OBJECT
  10. public:
  11. explicit sendthread(QWidget *parent=0);
  12. protected:
  13. void run();
  14. private:
  15. QHostAddress hostAddress;
  16. QUdpSocket udpsendsocket;
  17. NetBuffer sendBuffer;
  18. };
  19. #endif // SENDTHREAD_H

在 sendthread.h中定义了线程需要用到的主机地址 hostAddress、UDPsocket 端口和发送缓冲区,定义了线程需要重载的 run()操作。在 sendthread.cpp 的构造函数中,初始化参数,获取本机地址,绑定 socket 端口:

  1. #include "sendthread.h"
  2. sendthread::sendthread(QWidget *parent):
  3. QThread(parent)
  4. {
  5. QList<QHostAddress> addresslist=QNetworkInterface::allAddresses();
  6. hostAddress=addresslist.at(0);
  7. udpsendsocket.bind(hostAddress,7000);
  8. sendBuffer.data.index=0;
  9. }

然后重载实现 run()操作。这里要注意的是,由于主窗口的 ui变量是 protected 类型线程不能直接使用,需要线程通过主窗口的 displaySendData方法,将显示信息输出到界面中。

  1. #include<QTime>
  2. void sendthread::run(){
  3. while(true){
  4. QTime tm=QTime::currentTime();
  5. sendBuffer.data.hour=tm.hour();
  6. sendBuffer.data.minute =tm.minute();
  7. sendBuffer.data.second =tm.second();
  8. sendBuffer.data.msec=tm.msec();
  9. udpsendsocket.writeDatagram(sendBuffer.dataBuffer,sizeof(sendBuffer),hostAddress,7001);
  10. QString displaystring;
  11. displaystring=QString("Index=%1\nTime=%2:%3:%4.%5\n")
  12. .arg(sendBuffer.data.index)
  13. .arg(sendBuffer.data.hour,2,10,QChar('0'))
  14. .arg(sendBuffer.data.minute,2,10,QChar('0'))
  15. .arg(sendBuffer.data.second,2,10,QChar('0'))
  16. .arg(sendBuffer.data.msec,3,10,QChar('0'));
  17. ((MainWindow*)this->parent())->DisplaySendData(displaystring);
  18. sendBuffer.data.index++;
  19. this->sleep(1);
  20. }
  21. }

其中 recvthread.h 的定义:

  1. #include <QWidget>
  2. #include<QThread>
  3. #include<QtNetwork/QNetworkInterface>
  4. #include<QtNetwork/QHostAddress>
  5. #include<QtNetwork/QUdpSocket>
  6. #include "NetBuffer.h"
  7. class recvthread: public QThread
  8. {
  9. Q_OBJECT
  10. public:
  11. explicit recvthread(QWidget *parent=0);
  12. protected:
  13. void run();
  14. private:
  15. QHostAddress hostAddress;
  16. QUdpSocket udpRecvSocket;
  17. NetBuffer recvBuffer;
  18. };

和发送线程类似,定义了主机地址 hostAddress、UDPsocket 端口和发送缓冲区,定义了需要重载的 run()操作。在构造函数中,初始化接收 socket。

  1. recvthread::recvthread(QWidget *parent):
  2. QThread(parent)
  3. {
  4. QList<QHostAddress> addresslist=QNetworkInterface::allAddresses();
  5. hostAddress=addresslist.at(0);
  6. udpRecvSocket.bind(hostAddress,7001);
  7. }

在 run()中读取网络数据,并通过主窗口的 DisplayRecvData方法显示。注意这里使用了 waitForReadyRead方法以同步方式读取数据,而不是使用信号和槽的异步方法。当没有新数据到来时,线程处于挂起等待状态,当有数据到达时,立刻进入下一步处理,这种方法响应得更及时快速。

  1. #include"mainwindow.h"
  2. void recvthread::run(){
  3. while (true){
  4. if(udpRecvSocket.waitForReadyRead()){
  5. QHostAddress sender;
  6. quint16 senderPort;
  7. udpRecvSocket.readDatagram(recvBuffer.dataBuffer,sizeof(recvBuffer),&sender,&senderPort);
  8. QString displaystring;
  9. displaystring=QString("Index=%1\nTime=%2:%3:%4.%5\n")
  10. .arg(recvBuffer.data.index)
  11. .arg(recvBuffer.data.hour,2,10,QChar('0'))
  12. .arg(recvBuffer.data.minute,2,10,QChar('0'))
  13. .arg(recvBuffer.data.second,2,10,QChar('0'))
  14. .arg(recvBuffer.data.msec,3,10,QChar('0'));
  15. ((MainWindow*)this->parent())->DisplayRecvData(displaystring);
  16. }
  17. }

2.在主窗口中,初始化发送和接收 socket 线程,定义 DisplaySendData 和 DisplayRecvData操作显示收发数据。

  1. MainWindow::MainWindow(QWidget *parent)
  2. : QMainWindow(parent)
  3. , ui(new Ui::MainWindow)
  4. {
  5. ui->setupUi(this);
  6. sendthread *sendThread=new sendthread(this);
  7. recvthread *recvTrhead=new recvthread(this);
  8. recvTrhead->start();
  9. sendThread->start();
  10. }
  11. void MainWindow::DisplaySendData(QString displaystring){
  12. ui->listWidget->insertItem(0,displaystring);
  13. }
  14. void MainWindow::DisplayRecvData(QString displaystring){
  15. ui->listWidget_2->insertItem(0,displaystring);
  16. }


好啦!到这里这篇文章就结束啦!这就是本篇文章的全部内容了,接下来我还是会更新一些关于Qt基础编程的相关内容的!记得点点小爱心和关注哟!!!一起共同进步,交流学习!

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/花生_TL007/article/detail/556913
推荐阅读
相关标签
  

闽ICP备14008679号