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响应体积很大,就会导致界面渲染缓慢甚至被卡住。

相关推荐
做运维的阿瑞2 小时前
Python零基础入门:30分钟掌握核心语法与实战应用
开发语言·后端·python·算法·系统架构
Q_Q19632884752 小时前
python+spring boot洪涝灾害应急信息管理系统 灾情上报 预警发布 应急资源调度 灾情图表展示系统
开发语言·spring boot·python·django·flask·node.js·php
༾冬瓜大侠༿3 小时前
C语言:自定义类型——联合体和枚举
java·c语言·开发语言
饶了我吧,放了我吧4 小时前
数据通信与计算机网络-交换
网络
weixin_456904274 小时前
UDP端口释放和清理时间分析
网络·网络协议·udp
Lowjin_4 小时前
计算机网络-ipv4首部校验原理
网络·计算机网络
Net_Walke4 小时前
【网络协议】数字签名与证书
网络·网络协议
aramae5 小时前
Linux开发工具入门:零基础到熟练使用(二)
linux·运维·服务器·网络·笔记
Suckerbin6 小时前
burpsuite网络安全学院: JWT attacks靶场通关
网络·笔记·安全·web安全·网络安全