Qt 网络编程

概述

Qt 网络编程主要依赖其 Qt Network 模块,提供了跨平台的网络通信能力,支持 TCP、UDP、HTTP 等多种网络协议。下面从核心类、典型场景和示例代码三个方面介绍 Qt 网络编程的基础用法。

使用 Qt 网络功能前,需在项目文件(.pro)中添加模块依赖:

cpp 复制代码
QT += network  # 启用网络模块

常用核心类:

  • TCP 通信QTcpServer(服务器端,监听连接)、QTcpSocket(客户端 / 服务器端连接后的数据传输)
  • UDP 通信QUdpSocket(无连接的数据报传输)
  • HTTP 通信QNetworkAccessManager(管理 HTTP 请求)、QNetworkRequest(请求信息)、QNetworkReply(响应结果)

TCP 通信

TCP(Transmission Control Protocol)是一种被大多数Internet网路协议(如HTTP和FTP)用于数据传输的低级网络协议,它是可靠的、面向流、面向连接的传输协议,特别适合用于连续数据的传输。

TCP通信必须先建立TCP协议,通信端分为客户端和服务端。如下图

Qt提供QTcpSocket类和QTcpServer类用于建立TCP通信应用程序。服务器端程序必须使用QTcpServer用于端口监听,建立服务器;QTcpSocket用于建立连接后使用套接字进行通信。

QTcpServer是从QObject继承的类,它主要用于服务器端建立网络监听,创建网络Socket连接。QTcpServer类的主要接口函数入下:

服务器端程序首先需要用QTcpServer::listen()开始服务器端监听,可以指定监听的IP地址和端口,一般一个服务程序只监听某个端口的网络连接。当有新的客户端接入时,QTcpServer内部的incommingConnection函数会创建一个与客户端连接的QTcpSocket对象,然后发射信号newConnection函数。在newConnection信号的槽函数中,可以用nextPengdingConnection接受客户端的连接,然后使用QTcpSocket与客户端通信。所以在客户端与服务器建立TCP连接后,具体的数据通信是通过QTcpSocket完成的。QTcpSocket类提供了TCP协议的接口,可以用QTcpSocket类实现标准的网络通信协议入POP3、SMTP和NNTP,也可以设计自定义协议。

QTcpSocket是从QIODevice间接继承的类,所以具有流读写的功能,继承关系如下:

QTcpSocket类除了构造与析构外,其他函数都是从QAbstractSocket继承或者重定义的。QAbstractSocket用于TCP通信的主要接口函数如下:

TCP客户端使用QTcpSocket与Tcp服务器建立连接并通信。客户端的QTcpSocket实例首先通过connectToHost函数尝试连接到服务器,需要指定服务器的IP地址和端口。connectToHost函数是异步方式连接到服务器,不会阻塞程序运行,连接后发射connected信号。如果需要使用阻塞式方式连接服务器,则使用waitForConnected函数阻塞程序运行,直到连接成功或失败,例如:

cpp 复制代码
socket->connectToHost("192.168.100.100", 6666);
if(socket->waitForConnected(1000))
{
    qDebug("connected");
}

与服务器端建立socket连接后,就可以向缓冲区写数据或者从接收缓冲区读取数据,实现数据的通信。当缓冲区有新数据进入时,会触发readyRead信号,一般在此信号的槽函数里读取缓冲区数据。QTcpSocket是从QIODevice间接继承的,所以可以使用流数据读写功能。一个QTcpSocket实例既可以接收数据也可以发送数据,且接收与发射是异步工作的,有各自的缓冲区。

TCP网络编程流程图

官方文档中内容

服务器端的设计

ui设计

cpp 复制代码
//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpServer>

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;
    QTcpServer *_tcpSever;
};
#endif // WIDGET_H

//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QTcpSocket>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    //设置窗口标题
    setWindowTitle("TCP服务器");
    //初始化
    _tcpSever = new QTcpServer(this);
    //通过槽函数进行连接
    connect(_tcpSever, &QTcpServer::newConnection, this, &Widget::handle);
    //监听端口
    bool ret = _tcpSever->listen(QHostAddress::Any, 8888);
    if(!ret)
    {
        QMessageBox::critical(this, "服务器启动失败", _tcpSever->errorString());
        return;
    }
}

void Widget::handle()
{
    //获取到新的连接对应的socket.
    QTcpSocket *clientSocket = _tcpSever->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();
    });
}

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

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

客户端的设计

ui设计

cpp 复制代码
//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#include <QWidget>
#include <QTcpSocket>

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_btnSend_clicked();

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

//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    //设置窗⼝标题
    setWindowTitle("TCP客户端");
    //实例化socket对象
    _tcpClient = new QTcpSocket(this);
    //与服务器建⽴连接
    _tcpClient->connectToHost("127.0.0.1", 8888);

    //处理服务器返回的响应.
    connect(_tcpClient, &QTcpSocket::readyRead, this, [=](){
        //读取当前接收缓冲区中的所有数据
        QString response = _tcpClient->readAll();
        qDebug() << response;
        ui->listWidget->addItem(QString("服务器说: ") + response);
    });

    //等待并确认连接是否出错
    if(!_tcpClient->waitForConnected()){
        QMessageBox::critical(this, "连接服务器出错!", _tcpClient->errorString());
        return;
    }
}

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


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

测试结果

先启动服务器,然后再启动客户端,在客户端发送数据给服务器。

UDP 通信

UDP(User Datagram Protocol,用户数据报协议)是轻量的、不可靠的、面向数据报、无连接的协议,它可以用于对可靠性要求不高的场合。与TCP通信不同,两个程序之间进行UDP通信无需预先建立持久的socket连接,UDP每次发送数据报都需要指定目标地址与端口。

QUdpSocket类用于实现UDP通信,它从QAbstractSocket类继承,因而与QTcpSocket共享大部分的接口函数。主要区别是QUdpSocket以数据报传输数据,而不是以连续的数据流。发送数据使用函数QUdpSocket::writeDatagram(),数据报的长度一般少于512字节,每个数据报包含发送者和接受者的IP地址和端口等信息。

要进行UDP数据接收,要用QUdpSocket::bind()函数先绑定一个端口,用于接收传入的数据报。当有数据报传入时会发射readyRead()信号,使用readDatagram()函数来读取接收到的数据报。

QUdpSocket是从QAbstractSocket继承而来,但是又定义了较多新的功能函数用于实现UDP特有的一些功能。QUdpSocket的主要功能函数如下:

服务器端的设计

在ui界面,设计一个listWidget控件,用于显示客户端发送过来的数据

cpp 复制代码
//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 &req);

private:
    Ui::Widget *ui;

    QUdpSocket *_udpSocket;
};
#endif // WIDGET_H

//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QMessageBox>
#include <QNetworkDatagram>

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    //设置窗口标题
    setWindowTitle("UDP服务器端");

    //实例化socket
    _udpSocket = new QUdpSocket(this);

    //在收到数据并准备就绪后会触发readyRead信号,然后与handle函数建立连接
    connect(_udpSocket, &QUdpSocket::readyRead, this, &Widget::handle);

    //绑定端口号,如果绑定失败,会调用对话框报错
    bool ret = _udpSocket->bind(QHostAddress::Any, 8888);
    if(!ret)
    {
        QMessageBox::critical(this,  "服务器启动错误", _udpSocket->errorString());
        return;
    }
}

void Widget::handle()
{
    //1、读取请求,读取一个UDP数据报,并且获取数据
    const QNetworkDatagram &requestDatagram = _udpSocket->receiveDatagram();
    QString request = requestDatagram.data();

    //2、根据请求计算响应
    const QString &response = process(request);

    //3、将响应写回到客户端
    //封装数据报,并将其发送给客户端
    QNetworkDatagram responseDatagram(response.toUtf8(),
                                      requestDatagram.senderAddress(),
                                      requestDatagram.senderPort());
    _udpSocket->writeDatagram(responseDatagram);

    //显示打印日志
    QString log = "[" + requestDatagram.senderAddress().toString()
            + ":" + QString::number(requestDatagram.senderPort())
            + "] req: " + request + ", resp: " + response;
    ui->listWidget->addItem(log);
}

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

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

客户端的设计

在ui界面,设计一个listWidget空间,用于显示信息;Line Edit用于客户端数据的输入,PushButton用于将Line Edit中的数据发送给服务器。

cpp 复制代码
//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_btnSend_clicked();
    void handle();

private:
    Ui::Widget *ui;
    QUdpSocket *_udpClient;
};
#endif // WIDGET_H


//widget.cpp
#include "widget.h"
#include "ui_widget.h"

#include <QNetworkDatagram>
//提前定义好服务器的 IP 和 端⼝
const QString &SERVER_IP = "127.0.0.1";
const quint16 SERVER_PORT = 8888;

Widget::Widget(QWidget *parent)
    : QWidget(parent)
    , ui(new Ui::Widget)
{
    ui->setupUi(this);

    _udpClient = new QUdpSocket(this);
    setWindowTitle("UDP客户端");

    //在收到数据并准备就绪后会触发readyRead信号,然后与handle函数建立连接
    connect(_udpClient, &QUdpSocket::readyRead, this, &Widget::handle);
}

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

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

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

测试结果

先启动服务器,然后再启动客户端,在客户端发送数据给服务器。

HTTP通信

Qt网络模块提供一些类实现OSI七层网络模型中高层的网络协议,如HTTP、FTP、SNMP等,这些类主要是QNetworkRequest网络请求、QNetworkReply网络回复和QNetworkAccessManager网络访问管理器。其中,QNetworkRequest类通过一个URL地址发起网络协议请求,也保存网络请求的信息,目前支持HTTP、FTP和局部文件URLs的上传和下载。QNetworkAccessManager类用于协调网络操作。在QNetworkRequest发起一个网络请求后,QNetworkAccessManager类负责发送网络请求,创建网络响应。QNetworkReply类表示网络请求的响应。QNetworkAccessManager在发送一个网络请求后创建一个网络响应。QNetworkReply提供的信号finished、readyRead和downloadProgress可以检测网络响应的执行情况,执行响应操作。QNetworkReply是QIODevice的子类,所以QNetworkReply支持流读写功能,也支持异步或者同步工作模式。

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

  • 通过HTTP从服务器获取数据;

  • 通过HTTP向服务器提交数据

Http的代码设计

cpp 复制代码
//widget.h
#ifndef WIDGET_H
#define WIDGET_H

#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 *_httpManager;
};
#endif // WIDGET_H

//widget.cpp
#include "widget.h"
#include "ui_widget.h"
#include <QUrl>
#include <QNetworkReply>

Widget::Widget(QWidget *parent)
: QWidget(parent)
, ui(new Ui::Widget)
{
    ui->setupUi(this);

    setWindowTitle("Http客户端");

    _httpManager = new QNetworkAccessManager(this);
}

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

void Widget::on_pushButton_clicked()
{
    //获取到输⼊框中的URL, 构造QUrl对象
    QUrl url(ui->lineEdit->text());

    //构造HTTP请求对象
    QNetworkRequest request(url);

    //发送GET请求
    QNetworkReply *response = _httpManager->get(request);
    //通过信号槽来处理响应
    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();
    });
}

测试结果

如果使用的是http没有问题,但是如果使用https的时候有问题,可以按照如下操作:将libssl-1_1-x64.dll与libcrypto-1_1-x64.dll文件放在D:\software\qt5\5.14.2\mingw73_64\bin目录下(自己的就找到之前安装Qt软件包的位置),当然如果是32位系统,则是如下两个文件libssl-1_1.dll和libcrypto-1_1.dll(这两个文件我这里有,也可以网络下载)

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

相关推荐
xcyxiner23 分钟前
DicomViewer (vcpkg Windows和ubuntu编译)7
qt
Quz5 天前
QML Hello World 入门示例
qt
xcyxiner8 天前
DicomViewer (dcmtk读取dcm文件)5
qt
xcyxiner9 天前
DicomViewer (后台线程处理文件)4
qt
xcyxiner9 天前
DicomViewer (添加模型类)3
qt
xcyxiner10 天前
DicomViewer (目录调整) 2
qt
xcyxiner10 天前
dcmtk vtk vtk-dicom(gdcm) 编译(debug) v2
qt
LDR00612 天前
Type-C 快充全面升级!LDR6601 赋能个人护理便携电机,重塑剃须刀 / 理发器新体验
c语言·开发语言
雪碧聊技术12 天前
Tree.js是什么?一文讲透
开发语言·javascript·ecmascript
码云数智-园园12 天前
C++20 Modules 模块详解
java·开发语言·spring