【Qt网络】—— Qt网络编程

目录

(一)UDP Socket

1.1 核心API概览

 1.2  代码示例

1.2.1 回显服务器

1.2.2 回显客户端 

 (二)TCP Socket

2.1 核心API概览

2.2 代码示例

2.2.1 回显服务器

2.2.2 回显客户端

(三)HTTP Client

3.1 核心API

3.2 代码示例 

(四)其他模块

总结


和多线程类似,Qt为了支持跨平台,对网络编程的API也进行了重新封装。咱们接下来的重点介绍Qt的网络相关的API的使用。

注意: 

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

在进行网络编程之前,需要在项目中的 .pro 文件中添加 network 模块. 添加之后要手动编译⼀下项目,使QtCreator能够加载对应模块的头文件.


(一)UDP Socket

1.1 核心API概览

主要的类有两个. QUdpSocket QNetworkDatagram

QUdpSocket 表示⼀个UDP的socket⽂件.

QNetworkDatagram 表⽰⼀个UDP数据报.


 1.2  代码示例

1.2.1 回显服务器

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

  • 2) 创建 QUdpSocket 成员 .修改widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QUdpSocket>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();
    void handle();
    QString process(const QString& request);
private:
    Ui::Widget *ui;

    QUdpSocket* socket;

};
#endif // WIDGET_H

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

  • ⼀般来说,要先连接信号槽,再绑定端口.
  • 如果顺序反过来,可能会出现端口绑定好了之后,请求就过来了.此时还没来得及连接信号槽.那么这 个请求就有可能错过了. 
#include "widget.h"
#include "ui_widget.h"
#include<QMessageBox>
#include<QNetworkDatagram>
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    // 1. 设置窗⼝标题
    this->setWindowTitle("服务器");
    //实例化 socket
    socket = new QUdpSocket(this);
    //连接信号槽
    connect(socket,&QUdpSocket::readyRead,this,&Widget::handle);

    //绑定端口号
    bool res = socket->bind(QHostAddress::Any,9090);
    if (!res) {
        QMessageBox::critical(this, "服务器启动出错", socket->errorString());
        return;
    }
}

Widget::~Widget()
{
    delete ui;
}

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

  • 读取请求并解析
  • 根据请求计算响应
  • 把响应写回到客户端
void Widget::handle()
{
    // 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.2.2 回显客户端 

1) 创建界面.包含⼀个 QLineEdit ,QPushButton和QListWidget

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

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

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

3) 创建 QUdpSocket 成员 修改widget.h,定义成员

#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include<QUdpSocket>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pushButton_clicked();
    void handle();
private:
    Ui::Widget *ui;
    // 创建 socket 成员
    QUdpSocket* socket;
};
#endif // WIDGET_H

修改widget.cpp,初始化socket

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    // 1. 实例化 socket
    socket = new QUdpSocket(this);
    // 2. 设置窗⼝名字
    this->setWindowTitle("客户端");

    connect(socket,&QUdpSocket::readyRead,this,&Widget::handle);
}

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("");
}

void Widget::handle()
{
    //读取响应数据
    const QNetworkDatagram& responseDatagram = socket->receiveDatagram();
    QString response = responseDatagram.data();
    //把响应数据显示到界面上
    ui->listWidget->addItem(QString("服务器说: ") + response);
}

最终执行效果(记住先运行服务器在运行客户端


 (二)TCP Socket

2.1 核心API概览

核心类是两个: QTcpServer QTcpSocket

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

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

QByteArray⽤用于表⽰⼀个字节数组.可以很方便的和QString进行相互转换.

例如:

  • 使用QString的构造函数即可把QByteArray转成QString.
  • 使用QString的 toUtf8 函数即可把QString转成QByteArray. 

2.2 代码示例

2.2.1 回显服务器

1)创建界面.包含⼀个 QListWidget ,用于显示收到的数据

2) 创建 QTcpServer 并初始化 修改widget.h,添加 QTcpServer 指针成员 

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

     //创建QTcpServer 
    QTcpServer* tcpServer;
 };

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

  • 设置窗口标题
  • 实例化TCPserver.(父元素设为当前控件,会在父元素销毁时被⼀起销毁).
  • 通过信号槽,处理客户端建立的新连接.
  • 监听端口
Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);
    //设置窗口标题
   this->setWindowTitle("服务器");

    //初始化
    tcpserver = new QTcpServer(this);

    //通过槽函数连接
    connect(tcpserver,&QTcpServer::newConnection,this,&Widget::handle);

    //监听端口
    bool res = tcpserver->listen(QHostAddress::Any,9090);
    if(!res){
        QMessageBox::critical(this,"服务器启动失败",tcpserver->errorString());
        exit(1);
    }
}

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

  • 获取到新的连接对应的socket.
  • 通过信号槽,处理收到请求的情况
  • 通过信号槽,处理断开连接的情况
void Widget::handle()
{
    //获取到新的连接对应的socket.
    QTcpSocket* clientSocket = tcpserver->nextPendingConnection();
    QString log = QString('[') + clientSocket->peerAddress().toString() + ":"
                        + QString::number(clientSocket->peerPort()) + "] 客⼾端上线!";
    ui->listWidget->addItem(log);

    //通过信号槽,处理收到请求的情况
    connect(clientSocket,&QTcpSocket::readyRead,this,[=](){
       //读取出请求数据
        QString request = clientSocket->readAll();
       //根据请求处理响应
        const QString& response =  process(request);
        //写会客户端
        clientSocket->write(response.toUtf8());

        QString log = QString("[") + clientSocket->peerAddress().toString()
                        + ":" + QString::number(clientSocket->peerPort()) + "] req: " +
                        request + ", resp: " + response;
        ui->listWidget->addItem(log);
    });

    //通过信号槽,处理断开连接的情况
    connect(clientSocket,&QTcpSocket::disconnected,this,[=](){
        QString log = "[" + clientSocket->peerAddress().toString() + ":"
                        + QString::number(clientSocket->peerPort()) + "] 客⼾端下线!";
        ui->listWidget->addItem(log);
        clientSocket->deleteLater();
    });
}

 4) 实现process方法,实现根据请求处理响应. 由 于我们此处是实现回显服务器.所以process⽅法中并没有包含实质性的内容.

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

到此,服务器程序编写完毕.


2.2.2 回显客户端

1) 创建界面.包含⼀个 QLineEdit ,QPushButton和QListWidget

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

 2) 创建 QUdpSocket 成员 修改widget.h,定义成员

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
    QTcpSocket* socket;
};
#endif // WIDGET_H

 修改widget.cpp,初始化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.处理服务器返回的响应.
    connect(socket,&QTcpSocket::readyRead,this,[=](){
        //读取当前接收缓冲区中的所有数据
       QString response = socket->readAll();
       qDebug() << response;
       ui->listWidget->addItem(QString("服务器说: ") + response);
    });
    // 5. 等待并确认连接是否出错
    if(!socket->waitForConnected()){
        QMessageBox::critical(this,"连接服务器出错!",socket->errorString());
        exit(1);
    }
}

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

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

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


(三)HTTP Client

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

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

3.1 核心API

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

QNetworkAccessManager 提供了HTTP的核心操作.

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

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

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

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


3.2 代码示例 

1) 创建界面.包含⼀个 QLineEdit ,QPushButton和QListWidget

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

【说明】

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

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

#include <QWidget>
#include<QNetworkAccessManager>
QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT

public:
    Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;

    QNetworkAccessManager* manager;
};
#endif // WIDGET_H

3) 修改widget.cpp,创建实例

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

Widget::~Widget()
{
    delete ui;
}

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);
            // qDebug() << html;
            } else {
            // 响应出错
                ui->plainTextEdit->setPlainText(response->errorString());
            }
        response->deleteLater();
    });
}

执行程序,观察效果 


(四)其他模块

 Qt 中还提供了FTP,DNS,SSL等网络相关的组件工具.此处不再⼀⼀展开介绍.有需要的可以自行翻阅官方文档学习相关API的使用.


总结

Qt是一个跨平台的应用程序和用户界面框架,广泛用于开发图形用户界面程序,同时也提供了强大的网络编程能力。Qt的网络编程主要基于其网络模块,即QtNetwork模块。以下是一些关于Qt网络编程的小结:

TCP套接字编程

  • 使用QTcpSocket可以创建客户端和服务器端的TCP连接。客户端通过连接到服务器的IP地址和端口来建立连接,而服务器端则监听特定端口等待客户端的连接请求。

UDP套接字编程

  • QUdpSocket用于处理无连接的网络通信。它允许发送和接收UDP数据包。UDP不保证数据包的顺序或可靠性,但其开销较小,适用于对实时性要求较高的应用。

HTTP请求

  • QNetworkAccessManager是处理HTTP请求的核心类。它支持GET、POST、PUT、DELETE等多种HTTP方法。通过QNetworkRequest可以设置请求的URL、头部信息等,而QNetworkReply用于处理服务器返回的数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

起飞的风筝

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值