【Qt】网络

06-02 1229阅读
【Qt】网络

🌈 个人主页:Zfox_

🔥 系列专栏:Qt

【Qt】网络

目录

  • 一:🔥 Qt 网络概述
  • 二:🔥 UDP Socket
    • 🦋 代码示例
    • 三:🔥 TCP Socket
      • 🦋 代码示例
      • 三:🔥 Http Client
        • 🦋 API 概述
        • 🦋 代码示例
        • 四:🔥 【Qt】音视频
        • 五:🔥 共勉

          一:🔥 Qt 网络概述

          和多线程类似, Qt 为了支持跨平台,对网络编程的 API 也进行了重新封装.

          💡 注意:

          实际 Qt 开发中进行网络编程,也不一定使用 Qt 封装的网络 API,也有一定可能使用的是系统原生 API 或者其他第三方框架的 API.

          在进行网络编程之前,需要在项目中的 .pro 文件 中添加 network 模块,

          • 添加之后要手动编译一下项目,使 Qt Creator 能够加载对应模块的头文件

            二:🔥 UDP Socket

            主要的类就两个:QUdpSocket 和 QNetworkDatagram

            QUdpSocket 表示一个 UDP 的 socket 文件

            名称类型说明对标原生 API
            bind(const QHostAddress&, quint16)方法绑定指定端口号bind
            receiveDatagram()方法返回 QNetworkDatagram ,读取一个 UDP 数据报recvfrom
            writeDatagram(const QNetworkDatagram&)方法发送一个 UDP 数据报sendto
            readyRead信号在收到数据并准备就绪后触发无(类似于 IO 多路复用的通知机制)

            readyRead :当 socket 收到请求的时候,QUdpSocket 就会触发这个信号,此时就可以在槽函数中完成读取请求的操作了

            基于信号槽,就天然达成了 事件驱动 的效果

            QNetworkDatagram 表示一个 UDP 数据报

            名称类型说明对标原生 API
            QNetworkDatagram(const QByteArray&, const QHostAddress&, quint16)构造函数通过 QByteArray,目标 IP 地址,目标端口号 构造一个 UDP 数据报,通常用于发送数据时
            data()方法获取数据报内部持有的数据,返回 QByteArray
            senderAddress()方法获取数据报中包含的对端的 IP 地址无,recvfrom 包含了该功能
            senderPort()方法获取数据报中包含的对端的端口号无,recvfrom 包含了该功能

            🦋 代码示例

            回显服务器

            1)创建界面,包含一个 QListWidget 用来显示消息

            【Qt】网络

            2)创建 QUdpSocket 成员

            修改 widget.h

            class Widget : public QWidget
            {
            	Q_OBJECT
            public:
                Widget(QWidget *parent = nullptr);
                ~Widget();
            private:
                Ui::Widget *ui;
                QUdpSocket* socket;
            };
            

            修改 widget.cpp,完成 socket 后续的初始化

            一般来说,要先连接信号槽,再绑定端口.

            如果顺序反过来,可能会出现端口绑定好了之后,请求就过来了,此时还没来得及连接信号槽,那么这个请求就有可能错过了.

            Widget::Widget(QWidget *parent)
            : QWidget(parent)
            , ui(new Ui::Widget)
            {
                ui->setupUi(this);
                // 1. 设置窗⼝标题 
                this->setWindowTitle("服务器");
                // 2. 实例化 socket 
                socket = new QUdpSocket(this);
                // 3. 连接信号槽, 处理收到的请求 
                connect(socket, &QUdpSocket::readyRead, this, &Widget::processRequest);
                // 4. 绑定端⼝ 
                bool ret = socket->bind(QHostAddress::Any, 9090);
                if (!ret) {
                    QMessageBox::critical(nullptr, "服务器启动出错", socket->errorString());
                    return;
                }
            }
            

            3)实现 processRequest,完成处理请求的过程

            • 读取请求并解析
            • 根据请求计算响应
            • 把响应写回到客户端
              void Widget::processRequest()
              {
                  // 1. 读取请求 
                  const QNetworkDatagram& requestDatagram = socket->receiveDatagram();
                  QString request = requestDatagram.data();
                  // 2. 根据请求计算响应 
                  const QString& response = process(request);
                  // 3. 把响应写回到客⼾端 
                  QNetworkDatagram responseDatagram(response.toUtf8(), 
                  requestDatagram.senderAddress(), requestDatagram.senderPort());
                  socket->writeDatagram(responseDatagram);
                  // 显⽰打印⽇志 
                  QString log = "[" + requestDatagram.senderAddress().toString() + ":" + 
                  QString::number(requestDatagram.senderPort()) + "] req: " + request + ", resp: " + response;
                  ui->listWidget->addItem(log);
              }
              

              4)实现 process 函数

              由于 我们此处是实现回显服务器,所以 process 方法中并没有包含实质性的内容

              QString Widget::process(const QString& request)
              {
              	return request;
              }
              

              此时,服务器程序编写完毕。但是直接运行还看不出效果,还需要搭配客户端来使用

              回显客户端

              1)创建界面.包含一个 QLineEdit,QPushButton,QListwidget

              • 先使用水平布局把 QLineEdit 和 QPushButton 放好,并设置这两个控件的垂直方向的 sizePolicy 为Expanding
              • 再使用垂直布局把 QListwidget 和上面的水平布局放好
              • 设置垂直布局的 layoutStretch 为5,1(当然这个尺寸比例根据个人喜好微调)

                【Qt】网络

                2)在 widget.cpp 中,先创建两个全局常量,表示服务器的 IP 和 端口

                // 提前定义好服务器的 IP 和 端⼝ 
                const QString& SERVER_IP = "127.0.0.1";
                const quint16 SERVER_PORT = 9090;
                

                3)创建 QUdpsocket 成员

                修改 widget.h,定义成员

                class Widget : public QWidget
                {
                 Q_OBJECT
                public:
                    Widget(QWidget *parent = nullptr);
                    ~Widget();
                private:
                    Ui::Widget *ui;
                    // 创建 socket 成员 
                    QUdpSocket* socket;
                };
                

                4)给发送按钮 slot 函数,实现发送请求

                void Widget::on_pushButton_clicked()
                {
                    // 1. 获取到输⼊框的内容 
                    const QString& text = ui->lineEdit->text();
                    // 2. 构造请求数据 
                    QNetworkDatagram requestDatagram(text.toUtf8(), QHostAddress(SERVER_IP), 
                    SERVER_PORT);
                    // 3. 发送请求 
                    socket->writeDatagram(requestDatagram);
                    // 4. 消息添加到列表框中 
                    ui->listWidget->addItem("客⼾端说: " + text);
                    // 5. 清空输⼊框 
                    ui->lineEdit->setText("");
                }
                

                5)再次修改 Widget 的构造函数,通过信号槽处理服务器响应

                connect(socket, &QUdpSocket::readyRead, this, [=]() {
                    const QNetworkDatagram responseDatagram = socket->receiveDatagram();
                    QString response = responseDatagram.data();
                    ui->listWidget->addItem(QString("服务器说: ") + response);
                });
                

                结果如下:

                【Qt】网络

                三:🔥 TCP Socket

                主要的类就两个:QTcpSocket 和 QTcpServer

                QTcpSocket 用于监听端口,获取客户端连接

                名称类型说明对标原生 API
                listen(const QHostAddress&, quint16 port)方法绑定指定地址和端口号,并开始监听bind 和 listen
                nextPendingConnection()方法从系统中获取到⼀个已经建立好的 tcp 连接.

                返回⼀个 QTcpSocket, 表示这个客户端的连接.

                通过这个socket对象完成和客户端 之间的通信.

                accept
                newConnection信号有新的客户端端建立连接好之后触发无(但类似于 IO 多路复用中的通知机制)

                QTcpSocket 用于 客户端 和 服务器之间的数据交互

                名称类型说明对标原生 API
                readAll()构造函数读取当前接收缓冲区中的所有数据
                返回 QByteArray对象
                read
                write(const QByteArray&)方法把数据写入 socket 中write
                deleteLater方法暂时把 socket 对象标记为无效.Qt 会在下个事件循环中析构释放该对 象.无(但类似于 “半自动化的垃圾回收”)
                readyRead()信号有数据到达并准备就绪时触发无(但类似于 IO 多路复用中的通知机制)

                🦋 代码示例

                回显服务器

                1)创建界面,包含一个 QListWidget 用来显示消息

                【Qt】网络

                2)创建 QTcpSocket 并且初始化

                修改 widget.h,添加 QTcpServer 指针成员

                class Widget : public QWidget
                {
                	Q_OBJECT
                public:
                    Widget(QWidget *parent = nullptr);
                    ~Widget();
                private:
                    Ui::Widget *ui;
                    QTcpSocket* socket;
                };
                

                修改 widget.cpp,实例化 QTcpServer 并进行后续初始化操作,

                • 设置窗口标题
                • 实例化 TCP server.(父元素设为当前控件,会在父元素销毁时被一起销毁),
                • 通过信号槽,处理客户端建立的新连接
                • 监听端口
                  Widget::Widget(QWidget *parent)
                  : QWidget(parent)
                  , ui(new Ui::Widget)
                  {
                      ui->setupUi(this);
                      // 1. 设置窗⼝标题 
                      this->setWindowTitle("服务器");
                      // 2. 实例化 Tcp socket 
                      tcpsocket = new TcpSocket(this);
                      // 3. 通过信号槽, 处理客⼾端建⽴的新连接.  
                      connect(tcpServer, &QTcpServer::newConnection, this, &Widget::processConnection);
                      // 4. 监听端⼝ 
                      bool ret = tcpServer->listen(QHostAddress::Any, 9090);
                      if (!ret) {
                      	QMessageBox::critical(nullptr, "服务器启动失败!", tcpServer->errorString());
                      	exit(1);
                      }
                  }
                  

                  3)继续修改 widget.cpp,实现处理连接的具体方法 processconnection

                  • 获取到新的连接对应的 socket.
                  • 通过信号槽,处理收到请求的情况
                  • 通过信号槽,处理断开连接的情况
                    void Widget::processConnection()
                    {
                        // 1. 获取到新的连接对应的 socket.  
                        QTcpSocket* clientSocket = tcpServer->nextPendingConnection(); 
                        QString log = QString("[") + clientSocket->peerAddress().toString()
                         	+ ":" + QString::number(clientSocket->peerPort()) + "] 客⼾端上线!";
                        ui->listWidget->addItem(log);
                        // 2. 通过信号槽, 处理收到请求的情况 
                        connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
                            // a) 读取请求 
                            QString request = clientSocket->readAll();
                            // b) 根据请求处理响应 
                            const QString& response = process(request);
                            // c) 把响应写回客⼾端 
                            clientSocket->write(response.toUtf8());
                            QString log = QString("[") + clientSocket->peerAddress().toString()
                            + ":" + QString::number(clientSocket->peerPort()) + "] req: " + 
                            request + ", resp: " + response;
                            ui->listWidget->addItem(log);
                        });
                        
                        // 3. 通过信号槽, 处理断开连接的情况 
                        connect(clientSocket, &QTcpSocket::disconnected, this, [=]() {
                            QString log = QString("[") + clientSocket->peerAddress().toString()
                            	+ ":" + QString::number(clientSocket->peerPort()) + "] 客⼾端下线!";
                            ui->listWidget->addItem(log);
                            // 删除 clientSocket 
                            clientSocket->deleteLater();
                        });
                    }
                    

                    4)实现 process 函数

                    由于 我们此处是实现回显服务器,所以 process 方法中并没有包含实质性的内容

                    QString Widget::process(const QString& request)
                    {
                    	return request;
                    }
                    

                    此时,服务器程序编写完毕。但是直接运行还看不出效果,还需要搭配客户端来使用

                    回显客户端

                    1)创建界面.包含一个 QLineEdit,QPushButton,QListwidget

                    • 先使用水平布局把 QLineEdit 和 QPushButton 放好,并设置这两个控件的垂直方向的 sizePolicy 为Expanding
                    • 再使用垂直布局把 QListwidget 和上面的水平布局放好
                    • 设置垂直布局的 layoutStretch 为5,1(当然这个尺寸比例根据个人喜好微调)

                      【Qt】网络

                      2)创建QTcpSocket 并实例化,修改widget.h创建成员

                      class Widget : public QWidget
                      {
                       Q_OBJECT
                      public:
                          Widget(QWidget *parent = nullptr);
                          ~Widget();
                      private:
                          Ui::Widget *ui;
                          // 创建 QTcpsocket 
                          QTcpSocket* socket;
                      };
                      

                      修改 widget.cpp,对 QTcpSocket 进行实例化,

                      • 设置窗口标题
                      • 实例化 socket 对象(父元素设为当前控件,会在父元素销毁时被一起销毁)
                      • 和服务器建立连接
                      • 等待并确认连接是否出错
                        Widget::Widget(QWidget *parent)
                         : QWidget(parent)
                         , ui(new Ui::Widget)
                        {
                            ui->setupUi(this);
                            // 1. 设置窗⼝标题. 
                            this->setWindowTitle("客⼾端");
                            // 2. 实例化 socket 对象. 
                            socket = new QTcpSocket(this);
                            // 3. 和服务器建⽴连接. 
                            socket->connectToHost("127.0.0.1", 9090);
                            // 4. 等待并确认连接是否出错. 
                            if (!socket->waitForConnected()) {
                                QMessageBox::critical(nullptr, "连接服务器出错!", socket->errorString());
                                exit(1);
                        	}
                        }
                        

                        3)修改widget.cpp,给按钮增加点击的slot函数,实现发送请求给服务器

                        void Widget::on_pushButton_clicked()
                        {
                            // 获取输⼊框的内容 
                            const QString& text = ui->lineEdit->text();
                            // 清空输⼊框内容 
                            ui->lineEdit->setText("");
                            // 把消息显⽰到界⾯上 
                            ui->listWidget->addItem(QString("客⼾端说: ") + text);
                            // 发送消息给服务器 
                            socket->write(text.toUtf8());
                        }
                        

                        4)再次修改 Widget 的构造函数,通过信号槽处理服务器响应

                        // 处理服务器返回的响应. 
                        connect(socket, &QTcpSocket::readyRead, this, [=]() {
                            QString response = socket->readAll();
                            qDebug() listWidget->addItem(QString("服务器说: ") + response);
                        });
                        

                        先启动服务器,再启动客⼾端(可以启动多个),最终执行效果:

                        • 由于我们使用信号槽处理同⼀个客户端的多个请求,不涉及到循环,也就不会使客户端之间相互影响 了.

                          【Qt】网络

                          三:🔥 Http Client

                          进行 Qt 开发时,和服务器之间的通信很多时候也会用到 HTTP 协议.

                          • 通过 HTTP 从服务器获取数据
                          • 通过 HTTP 向服务器提交数据,

                            🦋 API 概述

                            关键类主要是三个,QNetworkAccessManager、QNetworkRequest,QNetworkReply

                            QNetworkAccessManager 提供了HTTP的核心操作,

                            方法说明
                            get(const QNetworkRequest&)发起一个 HTTP GET 请求,返回 QNetworkReply 对象
                            post(const QNetworkRequest&, const QByteArray&)发起一个 HTTP POST 请求。返回 QNetworkReply 对象

                            QNetworkRequest 表示⼀个HTTP请求(不含body).

                            • 如果需要发送⼀个带有body的请求(比如post),会在 QNetworkAccessManager 的 post方法 中通过单独的参数来传入 body
                              方法说明
                              QNetworkRequest(const QUrl &)通过 URL 构造 HTTP 请求
                              setHeader(QNetworkRequest::KnowHeaders header, const QVariant &value)设置请求头

                              其中的 QNetworkRequest::KnownHeaders 是⼀个枚举类型,常用取值:

                              取值说明
                              ContentTypeHeader描述 body 类型
                              ContentLengthHeader描述 body 长度
                              LocationHeader用于重定向报文中指定重定向地址(响应中使用,请求用不到)
                              CookieHeader设置 Cookie
                              UserAgenHeader设置 User-Agent

                              QNetworkReply 表示⼀个HTTP响应。这个类同时也是 QIODevice 的⼦类

                              方法说明
                              error()获取出错状态
                              errorString()获取出错原因的文本
                              readAll()读取响应 body
                              header(QNetworkRequest::KnownHeaders header)读取响应指定 header 的值

                              此外:QNetworkReply 还有⼀个重要的信号 finished 会在客户端收到完整的响应数据之后触发.

                              🦋 代码示例

                              1)创建界面:包含一个 QLineEdit,QPushButton、QPlainTextEidt

                              • 先使用水平布局把 QLineEdit 和 QPushButton 放好,并设置这两个控件的垂直方向的 sizePolicy 为Expanding
                              • 再使用垂直布局把 QPlainTextEidt 放好(QPlainTextEdit 的 readOnly 设为 true)
                              • 设置垂直布局的 layoutStretch 为5,1(当然这个尺寸比例根据个人喜好微调)

                                【Qt】网络

                                此处建议使用 QPlainTextEdit,而不是 QTextEdit,主要因为 QTextEdit 要进行 富文本解析,如果得到的HTTP响应体积很大,就会导致界面渲染缓慢甚至被卡住

                                2)修改 widget.h ,创建 QNetworkAccessManager 属性

                                class Widget : public QWidget
                                {
                                    Q_OBJECT
                                public:
                                    Widget(QWidget *parent = nullptr);
                                    ~Widget();
                                private:
                                    Ui::Widget *ui;
                                    QNetworkAccessManager* manager;
                                };
                                

                                3)修改 widget.cpp 创建实例

                                Widget::Widget(QWidget *parent)
                                    : QWidget(parent)
                                    , ui(new Ui::Widget)
                                {
                                    ui->setupUi(this);
                                    
                                    // 实例化属性
                                    manager = new QNetworkAccessManager(this);
                                }
                                

                                4)编写按钮的 slot 函数,实现发送 HTTP 请求功能

                                void Widget::on_pushButton_clicked()
                                {
                                    // 1. 获取输入到输入框的 URL,构造 QURL 对象
                                    QUrl url(ui->lineEdit->text());
                                    // 2. 构造 HTTP 请求对象
                                    QNetworkRequest request(url);
                                    // 3. 发送 GET 请求
                                    QNetworkReply* response = manager->get(request);
                                    // 4. 通过信号槽处理
                                    connect(response, &QNetworkReply::finished, this, [=](){
                                        if(response->error() == QNetworkReply::NoError){
                                            // 响应正确
                                            QString html(response->readAll());
                                            ui->plainTextEdit->setPlainText(html);
                                        }
                                        else{
                                            ui->plainTextEdit->setPlainText(response->errorString());
                                        }
                                        response->deleteLater();
                                    });
                                }
                                

                                执行程序,观察效果

                                【Qt】网络

                                四:🔥 【Qt】音视频

                                🔥 在Qt中,视频播放的功能主要是通过 QMediaPlayer 类和 QVideoWidget 类来实现。在使用这两个类时要添加对应的模块 multimedia 和 multimediawidgets

                                核心 API 概述

                                名称作用
                                setMedia()设置当前媒体源
                                setVideoOutput()将 QVideoWidget 视频输出附加到媒体播放器,如果媒体播放器已经添加了视频输出,将更换一个新的

                                代码示例

                                首先在 .pro 文件中添加 multimedia 和 multimediawidgets 两个模块;如下图示:

                                【Qt】网络

                                /********************************* widget.h *********************************/
                                #ifndef WIDGET_H
                                #define WIDGET_H
                                #include 
                                #include  //⽔平布局 
                                #include  //垂直布局 
                                #include  //显⽰视频 
                                #include  //播放声⾳ 
                                #include  //按钮 
                                #include  //设置图标 
                                #include  //选择⽂件/⽂件夹 
                                class Widget : public QWidget
                                {
                                    Q_OBJECT
                                public:
                                    Widget(QWidget *parent = nullptr);
                                    ~Widget();
                                    public slots:
                                    void chooseVideo();
                                private:
                                    QMediaPlayer *mediaPlayer;
                                    QVideoWidget *videoWidget;
                                    QVBoxLayout *vbox;
                                    
                                    //创建两个按钮:选择视频按钮和开播放按钮 
                                    QPushButton *chooseBtn,*playBtn;
                                };
                                #endif // WIDGET_H
                                /********************************* widget.cpp *********************************/
                                #include "widget.h"
                                #include 
                                #include 
                                Widget::Widget(QWidget *parent)
                                 : QWidget(parent)
                                {
                                    //对象实例化 
                                    mediaPlayer = new QMediaPlayer(this);
                                     videoWidget = new QVideoWidget(this);
                                    //设置播放画⾯的窗⼝ 
                                    videoWidget->setMinimumSize(600,600);
                                    //实例化窗⼝布局---垂直布局 
                                    this->vbox = new QVBoxLayout(this);
                                    this->setLayout(this->vbox);
                                    //实例化选择视频按钮 
                                    chooseBtn = new QPushButton("选择视频",this);
                                    //实例化播放按钮 
                                    playBtn = new QPushButton(this);
                                    //设置图标代替⽂件 
                                    playBtn->setIcon(this->style()->standardIcon(QStyle::SP_MediaPlay));
                                    //实例化⼀个⽔平布局,将以上控件放⼊⽔平布局中 
                                    QHBoxLayout *hbox = new QHBoxLayout;
                                     
                                    //添加控件 
                                    hbox->addWidget(chooseBtn);
                                    hbox->addWidget(playBtn);
                                     
                                    //将播放窗⼝和⽔平布局都添加到垂直布局中 
                                    vbox->addWidget(videoWidget);
                                    //布局中添加布局 
                                    vbox->addLayout(hbox);
                                    //将选择视频对应的按钮和槽函数进⾏关联 
                                    connect(chooseBtn,&QPushButton::clicked,this,&Widget::chooseVideo);
                                }
                                void Widget::chooseVideo()
                                {
                                    //选择视频,返回⼀个播放视频的名字 
                                    QString name = QFileDialog::getSaveFileName(this,"选择视频",".","WMV(*.wmv)");
                                    //设置媒体声⾳ 
                                    mediaPlayer->setMedia(QUrl(name));
                                    //输出视频画⾯
                                    mediaPlayer->setVideoOutput(videoWidget);
                                    //播放 
                                    mediaPlayer->play();
                                }
                                                                             
                                Widget::~Widget()
                                {
                                 
                                }
                                

                                五:🔥 共勉

                                😋 以上就是我对 【Qt】网络 的理解, 觉得这篇博客对你有帮助的,可以点赞收藏关注支持一波~ 😉

                                【Qt】网络

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

相关阅读

目录[+]

取消
微信二维码
微信二维码
支付宝二维码