【QT5】<总览五> QT多线程、TCP/UDP

打印 上一主题 下一主题

主题 575|帖子 575|积分 1729

文章目次

前言
一、QThread多线程
二、QT中的TCP编程
1. TCP简介
2. 服务端程序编写
3. 客户端程序编写
4. 服务端与客户端测试
三、QT中的UDP编程
1. UDP简介
2. UDP单播与广播程序

前言

承接【QT5】<总览四> QT常见绘图、图表及动画。若存在版权标题,请联系作者删除!

一、QThread多线程

1. 作用:创建多线程,防止应用程序界面卡顿。
2. 紧张操作:


  • ①创建的类需要继承QThread类。
  • ②重写run函数,新建的线程会执行run函数。
  • ③线程开启:对象调用start方法,使线程执行run函数。
  • ④线程终止:对象调用terminate方法,使线程不再执行run函数。
  • ⑤线程销毁:动态申请new需要调用deleteLater方法销毁线程对象。(该函数可以放置于run函数内,当run函数执行完毕后就会销毁该线程对象,防止内存泄漏)注意:尽量少用静态申请栈空间的方式创建线程对象,因为很可能该对象销毁时线程仍在执行,就会报错。
3. 实例演示:创建两个按钮,一个按钮点击时创建新线程,另一个按钮点击时终止新线程。
   【1】widget.h:
  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3. #include <QWidget>
  4. #include <QThread>
  5. QT_BEGIN_NAMESPACE
  6. namespace Ui { class Widget; }
  7. QT_END_NAMESPACE
  8. //自定义的线程类
  9. class MyThread : public QThread
  10. {
  11.     Q_OBJECT
  12. public:
  13.     MyThread(QWidget *parent = nullptr){
  14.         Q_UNUSED(parent)//防止编译器警告
  15.     }
  16.     ~MyThread(){
  17.         qDebug("线程销毁");
  18.     }
  19.     void run() override{
  20.         qDebug("线程开始");
  21.         sleep(5);//QThread里才有该方法
  22.         qDebug("线程停止");
  23.         deleteLater();//销毁线程
  24.     }
  25. };
  26. //Widget类
  27. class Widget : public QWidget
  28. {
  29.     Q_OBJECT
  30. public:
  31.     Widget(QWidget *parent = nullptr);
  32.     ~Widget();
  33. private slots:
  34.     void on_pushButton_clicked();
  35.     void on_pushButton_2_clicked();
  36. private:
  37.     Ui::Widget *ui;
  38.     MyThread *mythread;
  39. };
  40. #endif // WIDGET_H
复制代码
【2】widget.cpp:
  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. Widget::Widget(QWidget *parent)
  4.     : QWidget(parent)
  5.     , ui(new Ui::Widget)
  6. {
  7.     ui->setupUi(this);
  8. }
  9. Widget::~Widget()
  10. {
  11.     delete ui;
  12. }
  13. //“线程开始”按钮的槽函数
  14. void Widget::on_pushButton_clicked()
  15. {
  16.     mythread = new MyThread;
  17.     mythread->start();
  18. }
  19. //“线程终止”按钮的槽函数
  20. void Widget::on_pushButton_2_clicked()
  21. {
  22.     //若线程没有完成,则终止
  23.     if (!mythread->isFinished())
  24.         mythread->terminate();
  25. }
复制代码
【3】运行结果:
  

  • ①当我们点击“线程开始”按钮后,控制台打印“线程开始”,五秒后控制台打印“线程停止”和“线程销毁”。
  • ②当我们点击“线程开始”按钮后,控制台打印“线程开始”,当我们在五秒内点击“线程竣事”按钮,控制台什么都没有打印。
  
二、QT中的TCP编程

1. TCP简介



  • TCP是面向毗连的可靠的基于字节省的传输层通信协议。
  • TCP的服务端和客户端通信起首必须建立毗连
  • 建立毗连方式:服务端监听某个端口,当有客户端通过ip和port毗连时,就会创建一个socket毗连,之后就可以互发数据了。
  • QT中将socket视为输入输出流,数据的收发是通过read()write()来举行,而不是常见的send和recv。
----------------------------接下来,我们以一个实例来解析服务端和用户端程序编写-------------------------
2. 服务端程序编写

2.1 编写步调:
   【1】设置:①pro文件中添加network;②添加头文件<QTcpServer>和<QTcpSocket>。
  【2】创建服务端对象:QTcpServer *tcpServer;(详细分配空间在构造函数中)
  【3】服务端-客户端的信号槽毗连:connect(tcpServer, SIGNAL(newConnection()), this, SLOT(mNewConnection()));
  【4】编写【3】中的槽函数mNewConnection():
  

  • ①获取客户端对象:QTcpSocket *tmpTcpSocket = tcpServer->nextPendingConnection();
  • ②获取客户端信息:

    • 获取客户端ip:tmpTcpSocket->peerAddress().toString();
    • 获取客户端port:tmpTcpSocket->peerPort();

  • ③创建信号槽来处置惩罚客户端的毗连状态:connect(tmpTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(mStateChanged(QAbstractSocket::SocketState)));
  • ④创建信号槽来吸收客户端发送的信息:connect(tmpTcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
  【5】编写【4】中的槽函数mStateChanged(...):用switch-case布局来处置惩罚毗连状态,当状态为断开毗连时,删除当前调用的客户端对象。
  【6】编写【4】中的槽函数receiveMessage():调用tmpTcpSocket->readAll()来获取客户端发送的信息。
  【7】创建函数来给客户端发送数据:内部调用"客户端对象.write("写入的内容")"。
  【8】开始监听:调用tcpServer->listen(QHostAddress("192.168.124.151"), 9999); 监听ip为192.168.124.151,port为9999。
  2.2 编写代码:
   【1】widget.h:
  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3. #include <QWidget>
  4. #include <QTcpServer>
  5. #include <QTcpSocket>
  6. QT_BEGIN_NAMESPACE
  7. namespace Ui { class Widget; }
  8. QT_END_NAMESPACE
  9. class Widget : public QWidget
  10. {
  11.     Q_OBJECT
  12. public:
  13.     Widget(QWidget *parent = nullptr);
  14.     ~Widget();
  15. private slots:
  16.     void mNewConnection();
  17.     void receiveMessage();
  18.     void mStateChanged(QAbstractSocket::SocketState socketState);
  19.     void on_pushButton_3_clicked();
  20.     void on_pushButton_clicked();
  21.     void on_pushButton_2_clicked();
  22. private:
  23.     Ui::Widget *ui;
  24.     QTcpServer *tcpServer;
  25. };
  26. #endif // WIDGET_H
复制代码
【2】widget.cpp:
  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. /***********************************************************
  4.   * @函数名:Widget
  5.   * @功  能:构造函数---创建服务端对象,与客户端连接
  6.   * @参  数:parent---父对象
  7.   * @返回值:无
  8.   *********************************************************/
  9. Widget::Widget(QWidget *parent)
  10.     : QWidget(parent)
  11.     , ui(new Ui::Widget)
  12. {
  13.     ui->setupUi(this);
  14.     this->setWindowTitle("服务端");
  15.     //创建对象,与客户端连接
  16.     tcpServer = new QTcpServer(this);
  17.     connect(tcpServer, SIGNAL(newConnection()), this, SLOT(mNewConnection()));
  18. }
  19. /***********************************************************
  20.   * @函数名:~Widget
  21.   * @功  能:析构函数
  22.   * @参  数:无
  23.   * @返回值:无
  24.   *********************************************************/
  25. Widget::~Widget()
  26. {
  27.     delete ui;
  28. }
  29. /***********************************************************
  30.   * @函数名:mNewConnection
  31.   * @功  能:槽函数---若客户端发起连接,服务端连接客户端
  32.   * @参  数:无
  33.   * @返回值:无
  34.   *********************************************************/
  35. void Widget::mNewConnection()
  36. {
  37.     //获取客户端
  38.     QTcpSocket *tmpTcpSocket = tcpServer->nextPendingConnection();
  39.     //打印客户端的ip和port
  40.     QString ipAddr = tmpTcpSocket->peerAddress().toString();
  41.     quint16 port = tmpTcpSocket->peerPort();
  42.     ui->textBrowser->append("客户端的ip地址:" + ipAddr);
  43.     ui->textBrowser->append("客户端的端口:" + QString::number(port));
  44.     //处理客户端连接状态,接收客户端发送的数据
  45.     connect(tmpTcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
  46.     connect(tmpTcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
  47.             this, SLOT(mStateChanged(QAbstractSocket::SocketState)));
  48. }
  49. /***********************************************************
  50.   * @函数名:receiveMessage
  51.   * @功  能:槽函数---服务端接收客户端发送的数据
  52.   * @参  数:无
  53.   * @返回值:无
  54.   *********************************************************/
  55. void Widget::receiveMessage()
  56. {
  57.     QTcpSocket *tmpTcpSocket = (QTcpSocket*)sender();
  58.     ui->textBrowser->append("客户端:" + tmpTcpSocket->readAll());
  59. }
  60. /***********************************************************
  61.   * @函数名:mStateChanged
  62.   * @功  能:槽函数---服务端处理客户端的连接状态
  63.   * @参  数:socketState---客户端连接状态
  64.   * @返回值:无
  65.   *********************************************************/
  66. void Widget::mStateChanged(QAbstractSocket::SocketState socketState)
  67. {
  68.     QTcpSocket *tmpTcpSocket = (QTcpSocket*)sender();
  69.     //处理状态,删除断开的QTcpSocket对象
  70.     switch (socketState) {
  71.     case QAbstractSocket::UnconnectedState://断开连接,删除对象
  72.         ui->textBrowser->append("客户端断开连接");
  73.         tmpTcpSocket->deleteLater();
  74.         break;
  75.     case QAbstractSocket::ConnectedState://已连接
  76.         ui->textBrowser->append("客户端已连接");
  77.         break;
  78.     default:
  79.         break;
  80.     }
  81. }
  82. /***********************************************************
  83.   * @函数名:on_pushButton_3_clicked
  84.   * @功  能:按钮"发送消息"的槽函数,将文本信息发送给所有客户端
  85.   * @参  数:无
  86.   * @返回值:无
  87.   *********************************************************/
  88. void Widget::on_pushButton_3_clicked()
  89. {
  90.     QList <QTcpSocket*> clients = tcpServer->findChildren<QTcpSocket*>();
  91.     for (int i = 0; i < clients.length(); ++i) {
  92.         clients[i]->write(ui->lineEdit->text().toUtf8());
  93.     }
  94. }
  95. /***********************************************************
  96.   * @函数名:on_pushButton_clicked
  97.   * @功  能:按钮"开始监听"的槽函数,监听指定的ip和port
  98.   * @参  数:无
  99.   * @返回值:无
  100.   *********************************************************/
  101. void Widget::on_pushButton_clicked()
  102. {
  103.     tcpServer->listen(QHostAddress("192.168.124.151"), 9999);
  104. }
  105. /***********************************************************
  106.   * @函数名:on_pushButton_2_clicked
  107.   * @功  能:按钮"停止监听"的槽函数
  108.   * @参  数:无
  109.   * @返回值:无
  110.   *********************************************************/
  111. void Widget::on_pushButton_2_clicked()
  112. {
  113.     tcpServer->close();
  114. }
复制代码
3. 客户端程序编写

3.1 编写步调:
   【1】设置:①pro文件中添加network;②添加头文件<QTcpSocket>和<QHostAddress>。
  【2】创建客户端对象:QTcpSocket *tcpSocket;(详细分配空间在构造函数中)
  【3】创建信号槽来处置惩罚客户端的毗连状态:connect(tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)), this, SLOT(mStateChanged(QAbstractSocket::SocketState)));
  【4】创建信号槽来吸收客户端发送的信息:connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
  【5】编写【3】中的槽函数mStateChanged(...):switch-case处置惩罚。
  【6】编写【4】中的槽函数receiveMessage(): 调用tcpSocket->readAll().
  【7】创建函数来给服务端发送数据:内部调用"客户端对象.write("写入的内容")"。
  【8】启动毗连服务端:调用tcpSocket->connectToHost(QHostAddress("192.168.124.151"), 9999);
  【9】断开毗连服务端:调用tcpSocket->disconnectFromHost();
  3.2 编写代码:
   【1】widget.h:
  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3. #include <QWidget>
  4. #include <QTcpSocket>
  5. #include <QHostAddress>
  6. QT_BEGIN_NAMESPACE
  7. namespace Ui { class Widget; }
  8. QT_END_NAMESPACE
  9. class Widget : public QWidget
  10. {
  11.     Q_OBJECT
  12. public:
  13.     Widget(QWidget *parent = nullptr);
  14.     ~Widget();
  15. private slots:
  16.     void receiveMessage();
  17.     void mStateChanged(QAbstractSocket::SocketState socketstate);
  18.     void on_pushButton_3_clicked();
  19.     void on_pushButton_clicked();
  20.     void on_pushButton_2_clicked();
  21. private:
  22.     Ui::Widget *ui;
  23.     QTcpSocket *tcpSocket;
  24. };
  25. #endif // WIDGET_H
复制代码
【2】widget.cpp:
  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. /***********************************************************
  4.   * @函数名:Widget
  5.   * @功  能:构造函数---创建客户端对象,与服务端连接
  6.   * @参  数:parent---父对象
  7.   * @返回值:无
  8.   *********************************************************/
  9. Widget::Widget(QWidget *parent)
  10.     : QWidget(parent)
  11.     , ui(new Ui::Widget)
  12. {
  13.     //ui部分
  14.     ui->setupUi(this);
  15.     this->setWindowTitle("客户端");
  16.     ui->pushButton->setEnabled(true);
  17.     ui->pushButton_2->setEnabled(false);
  18.     //tcp部分
  19.     tcpSocket = new QTcpSocket(this);
  20.     connect(tcpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
  21.     connect(tcpSocket, SIGNAL(stateChanged(QAbstractSocket::SocketState)),
  22.             this, SLOT(mStateChanged(QAbstractSocket::SocketState)));
  23. }
  24. /***********************************************************
  25.   * @函数名:~Widget
  26.   * @功  能:析构函数
  27.   * @参  数:无
  28.   * @返回值:无
  29.   *********************************************************/
  30. Widget::~Widget()
  31. {
  32.     delete ui;
  33. }
  34. /***********************************************************
  35.   * @函数名:receiveMessage
  36.   * @功  能:槽函数---客户端接收服务端发送的数据
  37.   * @参  数:无
  38.   * @返回值:无
  39.   *********************************************************/
  40. void Widget::receiveMessage()
  41. {
  42.     ui->textBrowser->append("服务端:" + tcpSocket->readAll());
  43. }
  44. /***********************************************************
  45.   * @函数名:mStateChanged
  46.   * @功  能:槽函数---客户端连接状态改变的处理
  47.   * @参  数:socketstate---当前连接状态
  48.   * @返回值:无
  49.   *********************************************************/
  50. void Widget::mStateChanged(QAbstractSocket::SocketState socketstate)
  51. {
  52.     switch (socketstate) {
  53.     case QAbstractSocket::UnconnectedState:
  54.         ui->textBrowser->append("与服务端断开连接");
  55.         ui->pushButton->setEnabled(true);
  56.         ui->pushButton_2->setEnabled(false);
  57.         break;
  58.     case QAbstractSocket::ConnectedState:
  59.         ui->textBrowser->append("与服务端成功连接");
  60.         ui->pushButton->setEnabled(false);
  61.         ui->pushButton_2->setEnabled(true);
  62.         break;
  63.     default:
  64.         break;
  65.     }
  66. }
  67. /***********************************************************
  68.   * @函数名:on_pushButton_3_clicked
  69.   * @功  能:"发送消息"按钮的槽函数,必须连接了服务端才发送
  70.   * @参  数:无
  71.   * @返回值:无
  72.   *********************************************************/
  73. void Widget::on_pushButton_3_clicked()
  74. {
  75.     if (tcpSocket->state() == QAbstractSocket::ConnectedState){
  76.         tcpSocket->write(ui->lineEdit->text().toUtf8());
  77.     }
  78.     else {
  79.         ui->textBrowser->append("请先连接服务端!");
  80.     }
  81. }
  82. /***********************************************************
  83.   * @函数名:on_pushButton_clicked
  84.   * @功  能:"连接服务端"按钮的槽函数
  85.   * @参  数:无
  86.   * @返回值:无
  87.   *********************************************************/
  88. void Widget::on_pushButton_clicked()
  89. {
  90.     tcpSocket->connectToHost(QHostAddress("192.168.124.151"), 9999);
  91. }
  92. /***********************************************************
  93.   * @函数名:on_pushButton_2_clicked
  94.   * @功  能:"断开服务端"按钮的槽函数
  95.   * @参  数:无
  96.   * @返回值:无
  97.   *********************************************************/
  98. void Widget::on_pushButton_2_clicked()
  99. {
  100.     tcpSocket->disconnectFromHost();
  101. }
复制代码
4. 服务端与客户端测试

4.1 注意事项:


  • 服务端中的"开始监听"和"停止监听"应该设置成互斥的。方法一:在ui设计器里将它们添加到一个按钮组,然后选中"exclusive",将它们的"checkable"勾选上,再将"停止监听"按钮的"checked"勾选。方法二:利用代码ui->pushButton->setEnable(对/错);实现它们逻辑的互斥。
  • 客户端中的"毗连服务端"和"断开服务端"应该用上述的方法二实现互斥。
  • 在服务端中,当客户端断开毗连时,直接调用delete可能会出错(其他地方可能还在用这个变量)。因此,应当利用tmpTcpSocket.deleteLater(); 来删除客户端对象。
4.2 运行结果:


三、QT中的UDP编程

1. UDP简介



  • 是一个轻量级的,不可靠的,面向数据报的无毗连协议。
  • 通常音频、视频和普通数据在传送时利用 UDP 较多。
  • UDP 消息传送有三种模式:单播、广播和组播三种模式。
2. UDP单播与广播程序

2.1 编写步调:
   【1】设置:①pro文件中添加network;②添加头文件<QUdpSocket>。
  【2】创建UDP对象:QUdpSocket *udpSocket;(详细分配空间在构造函数中)
  【3】编写函数绑定端口:调用udpSocket->bind(端口号);
  【4】编写函数排除绑定:调用udpSocket->abort();
  【5】“发送-吸收”的信号槽毗连:connect(udpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
  【6】编写【5】中的receiveMessage来获取吸收的数据:内部调用udpSocket->readDatagram(mesg.data(), mesg.size(), &receiveAddress, &port);
  【7】编写函数来单播发送数据:调用udpSocket->writeDatagram(ui->lineEdit->text().toUtf8(), QHostAddress("127.0.0.1"), 9999);
  【8】编写函数来广播发送数据:与【7】雷同,只是将倒数第二个参数换成QHostAddress::Broadcast。要广播几个ip和端口,就调用几次writeDatagram。
  【8】要想获取当前毗连的状态,操作和TCP中的一样。
  2.2 实例代码:
   【1】widget.h:
  1. #ifndef WIDGET_H
  2. #define WIDGET_H
  3. #include <QWidget>
  4. #include <QUdpSocket>
  5. QT_BEGIN_NAMESPACE
  6. namespace Ui { class Widget; }
  7. QT_END_NAMESPACE
  8. class Widget : public QWidget
  9. {
  10.     Q_OBJECT
  11. public:
  12.     Widget(QWidget *parent = nullptr);
  13.     ~Widget();
  14. private slots:
  15.     void receiveMessage();
  16.     void on_pushButton_3_clicked();
  17.     void on_pushButton_clicked();
  18.     void on_pushButton_2_clicked();
  19.     void on_pushButton_4_clicked();
  20. private:
  21.     Ui::Widget *ui;
  22.     QUdpSocket *udpSocket;
  23. };
  24. #endif // WIDGET_H
复制代码
【2】widget.cpp:
  1. #include "widget.h"
  2. #include "ui_widget.h"
  3. /* 构造函数 */
  4. Widget::Widget(QWidget *parent)
  5.     : QWidget(parent)
  6.     , ui(new Ui::Widget)
  7. {
  8.     ui->setupUi(this);
  9.     ui->pushButton->setEnabled(true);
  10.     ui->pushButton_2->setEnabled(false);
  11.     //创建udp对象
  12.     udpSocket = new QUdpSocket(this);
  13.     connect(udpSocket, SIGNAL(readyRead()), this, SLOT(receiveMessage()));
  14. }
  15. /* 析构函数 */
  16. Widget::~Widget()
  17. {
  18.     delete ui;
  19. }
  20. /* 接收信息的槽函数*/
  21. void Widget::receiveMessage()
  22. {
  23.     QByteArray mesg;
  24.     mesg.resize(udpSocket->pendingDatagramSize());
  25.     QHostAddress receiveAddress;
  26.     quint16 port;
  27.     while (udpSocket->hasPendingDatagrams()) {
  28.         udpSocket->readDatagram(mesg.data(), mesg.size(), &receiveAddress, &port);//保存接收的数据
  29.         ui->textBrowser->append("接收来自:" + receiveAddress.toString() + ", 端口:" + QString::number(port));
  30.         ui->textBrowser->append("接收信息:" + mesg);
  31.     }
  32. }
  33. /* 发送信息按钮的槽函数 */
  34. void Widget::on_pushButton_3_clicked()
  35. {
  36.     udpSocket->writeDatagram(ui->lineEdit->text().toUtf8(), QHostAddress("127.0.0.1"), 9999);
  37.     ui->textBrowser->append("发送信息:" + ui->lineEdit->text().toUtf8());
  38. }
  39. /* 绑定端口按钮的槽函数 */
  40. void Widget::on_pushButton_clicked()
  41. {
  42.     udpSocket->bind(9999);
  43.     ui->textBrowser->append("当前绑定端口:" + QString::number(9999));
  44.     ui->pushButton->setEnabled(false);
  45.     ui->pushButton_2->setEnabled(true);
  46. }
  47. /* 解除绑定按钮的槽函数 */
  48. void Widget::on_pushButton_2_clicked()
  49. {
  50.     udpSocket->abort();
  51.     ui->textBrowser->append("解除绑定");
  52.     ui->pushButton->setEnabled(true);
  53.     ui->pushButton_2->setEnabled(false);
  54. }
  55. /* 广播信息按钮的槽函数 */
  56. void Widget::on_pushButton_4_clicked()
  57. {
  58.     udpSocket->writeDatagram(ui->lineEdit->text().toUtf8(), QHostAddress::Broadcast, 9999);
  59.     ui->textBrowser->append("发送信息:" + ui->lineEdit->text().toUtf8());
  60. }
复制代码
【3】ui设计器:
  

  【4】运行结果:双开该程序,每次新运行程序前修改一下绑定的端口号并编译。同时,这两个端口号需要对应才能显示单播的结果。
  

  





免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

王國慶

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表