跳到主要内容
极客日志极客日志面向AI+效率的开发者社区
首页博客GitHub 精选镜像工具UI配色美学隐私政策关于联系
搜索内容 / 工具 / 仓库 / 镜像...⌘K搜索
注册
博客列表
C++

C++ Qt 网络编程:QUdpSocket、QTcpSocket 与 Http 实战

Qt 网络编程涵盖 UDP、TCP 及 HTTP 协议实现。文章详解 QUdpSocket 与 QNetworkDatagram 用于无连接通信,演示 UDP 回显服务端与客户端代码。针对 TCP 流式传输,介绍 QTcpServer 监听端口及 QTcpSocket 处理连接,包含粘包问题的说明。HTTP 部分基于 QNetworkAccessManager 封装 GET/POST 请求,利用信号槽处理异步响应。提供完整源码示例,强调 .pro 文件配置 network 模块及信号槽连接顺序的重要性。

微码行者发布于 2026/3/28更新于 2026/6/825 浏览
C++ Qt 网络编程:QUdpSocket、QTcpSocket 与 Http 实战

C++ Qt 网络编程:QUdpSocket、QTcpSocket 与 Http 实战

绪论

本章将介绍 Qt 中的网络部分。在看这篇文章之前需要有一定的网络基础,也就是 TCP/HTTP。本篇文章主要讲到的是 Qt 中基础的 UDP、TCP、Http 的使用方法,并附有了多个小 demo 方便实操练习,并且其中还在每章最后进行了小总结回顾重要接口和函数方便回顾。

网络编程主要依赖于操作系统提供的 Socket API。需要注意的是,C++ 标准库本身并未封装网络编程相关的 API。

关于 Qt 网络编程的几个要点:

  1. 网络应用开发本质上是编写应用层代码,需要传输层协议(如 TCP/UDP)的支持
  2. 为此,Qt 提供了两套专门的网络编程 API(QUdpSocket 和 QTcpSocket)
  3. 使用 Qt 网络编程 API 时,需先在 .pro 文件中添加 network 模块
  4. 之前学习的 Qt 控件和核心功能都属于 QtCore 模块(默认已包含)

为什么 Qt 要划分出这些模块呢?

Qt 本身是一个非常庞大,包罗万象的框架。如果把所有功能都放到一起那么即使一个简单的程序,都会生成很大的可执行程序。所以进行一个模块化处理,将其他的功能分别封装成不同的模块。默认情况下额外模块不会参与编译,需要在 .pro 文件中,引入对应的模块,才能把对应功能给编译加载。Qt 其实提供了静态库的版本(所有都引入)、动态库的版本(指定引入)。

1. QUdpSocket

核心 API 概览

主要的类有两个:

  1. QUdpSocket(同 Linux 理解的 socket 概念它本质是打开的一个文件描述符,通过这个文件进行通信)
  2. QNetworkDatagram(是 Qt 对 UDP 数据报的完整封装,不仅包含二进制数据,还包含了发送/接收的地址端口等元信息,提供了比原始二进制数据处理更方便的面向对象接口。)

QUdpSocket:

名称类型说明对标原生 API
bind(const QHostAddress&, quint16)方法绑定指定的端口号bind
receiveDatagram()方法返回 QNetworkDatagram 读取一个 UDP 数据报recvfrom
writeDatagram(const QNetworkDatagram&)方法发送一个 UDP 数据报sendto
readyRead信号在收到数据并准备就绪后触发,当 socket 收到请求的时候,QUdpSocket 就会触发这个信号,此时就可以在槽函数里完成请求的操作无 (类似于 IO 多路复用的通知机制)

QNetworkDatagram:

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

实操 Demo

UDP 回显服务器

实现一个带有界面的 UDP 回显服务器(一般来说正经服务器,很少有界面化,一般都是命令行)

  1. 创建 Widget 项目
  2. 创建一个 QListWidget 表示通过 内部的 item 表示若干条消息
  3. 创建 QUdpSocket 成员变量 socket 句柄
  4. 构造函数中
    1. 初始化 socket 对象
    2. 设置窗口标题
    3. 连接信号槽 接收来自 socket 的 QUdpSocket 中的 readyRead 信号,触发槽函数
  5. 进行绑定端口号(一定是先连接信号槽,在绑定端口)
    1. socket 成员函数 bind
    2. 第一个参数 ip(任意 ip 也就是 QHostAddress::Any 宏)
    3. 第二个参数 端口号
    4. 一个端口号只能被一个 socket 绑定,所以可能失败,此时就需要进行防御性编程
    5. 通过 bind 的 bool 类型的返回值知道是否成功
    6. 若失败则时可以调用 QMessageBox 对话框提示
      1. 可以使用 critical(严重)方法
      2. 标题可以提示:服务器启动错误
      3. 文本可以填写:socket 的成员函数 errorString 显示错误(相当于 linux 中的 perror,本质上也是对相同的 error 的封装,存储着当前的错误信息,当调用的时候就会返回)
      4. 直接 返回 结束
  6. 槽函数(processRequest):
    1. 读取请求并解析
      1. 通过 socket 对象的 receiveDatagram(对比 c 中的 recv)获取请求
      2. 再通过数据报进行类型 QNetworkDatagram 进行接收(requestDatagram)
      3. 再将该对象调用 data 函数变成 QString 类型方便使用
    2. 根据请求计算响应(因为是回显服务器,需要不用操作,就是请求本身)
      1. 新增一个函数 process,内部处理获取到的请求,内部并响应
      2. QString process(QString&) 函数:
        1. 就直接返回 request 即可(因为是一个回显服务器,对于成熟的项目来说这里会比较复杂,也就是主要的业务逻辑!)
    3. 把响应写回给客户端
      1. 将 process 处理完后返回回来的值(response)调用 toUtf8 函数,也就是将 QString 转换为字节数组(这里本质是需要 QByteArray 的,但使用 Utf8 是因为他和 QByteArray 都是二进制的,所以可以互通)
      2. 换句话说是因为 QNetworkDatagram 是需要 QByteArray 参数进行构造的
      3. 在设置返回的 ip 地址和端口,它包含在 QNetworkDatagram(这里需要注意是当时接收请求的对象中的 ip 和端口)内部函数 senderAddress、sendPort 中
    4. 最后通过 socket 对象调用 writeDatagram
    5. 把这次交互的信息显示在界面上
      1. 构造一个 QString 显示内容变量 log:ip(根据上述设置 ip 和端口处一样)、端口(QString::number 将字符变成整形)、请求报文数据、响应数据
      2. 然后给 listwidget 多元素控制器添加子项目 addItem,构造就填入上述的显示内容 log 即可

注意在 .pro 加入 network

主要源码如下:

// widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QUdpSocket>
#include <QHostAddress>
#include <QMessageBox>
#include <QNetworkDatagram>
#include <QDateTime>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

private:
    Ui::Widget *ui;
    QUdpSocket* socket;
    QString process2(const QString& s);

private slots:
    void process();
};
#endif // WIDGET_H

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

Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    socket = new QUdpSocket(parent);
    ui->listWidget->setObjectName(QString("UDP 服务器"));
    connect(socket, &QUdpSocket::readyRead, this, &Widget::process);
    // 注意一定要先连接槽函数在绑定端口号
    bool isconnect = socket->bind(QHostAddress::Any, 8081); // QHostAddress::Any 宏代表任意 ip 连接
    if(!isconnect){
        QMessageBox::critical(this, "错误", "服务器启动错误"); // 弹窗提示
        return;
    }
    setWindowTitle("Udp 服务端");
}

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

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

void Widget::process()
{
    // 1. 读取请求并解析
    QNetworkDatagram requestPacket = socket->receiveDatagram();
    QString request = requestPacket.data();
    // QString s(UdpPacket.data());
    // QString s2 = QString::fromUtf8(UdpPacket.data());
    
    QString response = process2(request);
    
    QNetworkDatagram responsePacket(response.toUtf8(), requestPacket.senderAddress(), requestPacket.senderPort());
    // 使用 Utf8 是因为他和 QByteArray 都是二进制的
    // 时间戳部分没有写在步骤里是因为这部分是临时起意可以直接 copy 哟~
    socket->writeDatagram(responsePacket);
    
    qint64 msTimestamp = QDateTime::currentMSecsSinceEpoch();
    QDateTime dateTime = QDateTime::fromMSecsSinceEpoch(msTimestamp);
    QString formatted = dateTime.toString("yyyy-MM-dd hh:mm:ss.zzz");
    QString log = "[" + formatted + " : " + requestPacket.senderAddress().toString() + ":" + QString::number(requestPacket.senderPort()) + "] req: " + request + ", resp: " + response;
    ui->listWidget->addItem(log);
}
UDP 回显客户端 (UDPClient)
  1. 打开 ui 文件拖拽(要保证可以主动给服务器发起请求)
  2. 创建 QUdpSocket 类型的成员变量 socket
  3. 构造函数
    1. 添加两个全局变量 SERVER_IP 127.0.0.1(QString)
    2. SERVER_PORT 9090(quint16:端口号是两个字的 无符号整数 类似 unsigned short,只不过此处的不同于 short 他是固定就是 2 字节的)
    3. 初始化 socket 对象挂在对象树上
    4. 修改窗口标题 setWindowTitle
  4. 给按钮添加点击信号的槽函数
    1. 获取输入框的内容
    2. 构造 UDP 的请求数据包,创建 QNetworkDatagram 对象
      1. 并构造初始化传入获取的内容并且转成字节数组 QByteArray(调用 toUtf8)
      2. 还要传入 ip 和端口(其中 ip 的 QString 无法直接使用要转换成 QHostAddress 才能使用)
    3. 发送请求数据,调用 writeDatagram(使用之前的 socket)
    4. 把发送的请求也添加到列表框中,也就是给多元素控件添加到一个小项目并构造为 客户端说:text,直接使用第一个构造函数传递 QString 即可
    5. 最后把输入框中的内容清空 clear

输入框(单行 LineEdit + 多元素控件 ListWidget),发送按钮 pushButton,显示服务器返回的内容,和显示已经发送的内容

同样对于客户端也要处理下服务器返回回来的响应

  1. 通过信号槽,来处理服务器的数据
    1. 同样是通过 readyRead 信号,触发信号槽
  2. 信号槽 processResponse
    1. 通过这个函数处理收到的响应
      1. 读取响应数据 receiveDatagram,返回一个 QNetworkDatagram 报文类型变量
      2. 再通过 data 函数将数据报装换成 QString
    2. 把响应数据显示到界面
      1. 添加多元素控件添加 子项目:服务器说:text

此处打印的 ip 为 ffff 它是 ipv6 的环回 ip

可以通过 build 生成的 exe 文件打开多个客户端

那现在能否将这个 UDP 服务器放到服务器上呢?

大概率不行。。(取决于 Qt 程序是否安装了图形化界面,因为 Qt 是依赖图形化界面来运行的)

能否使用现在的 Udp 客户端连之前 Linux 阶段的 Udp 服务器—完全 OK! 一般也就是说服务器并不会那 Qt 写,Qt 常用于编写客户端

主要源码如下:

// widget.h
#ifndef WIDGET_H
#define WIDGET_H
#include <QWidget>
#include <QUdpSocket>
#include <QNetworkDatagram>
#include <QHostAddress>

QT_BEGIN_NAMESPACE
namespace Ui { class Widget; }
QT_END_NAMESPACE

class Widget : public QWidget
{
    Q_OBJECT
public:
    explicit Widget(QWidget *parent = nullptr);
    ~Widget();

private slots:
    void on_pushButton_clicked();

private:
    Ui::Widget *ui;
    QUdpSocket *socket;

private slots:
    void processResponse();
};
#endif // WIDGET_H

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

QString SERVER_IP = "127.0.0.1";
quint16 SERVER_PORT = 8081; // unsigned short

Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    socket = new QUdpSocket(parent);
    setWindowTitle("Udp 客户端");
    // 接收处理服务器发送回来的数据
    connect(socket, &QUdpSocket::readyRead, this, &Widget::processResponse);
    // 通过 readyRead 信号知道消息准备完毕
}

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

void Widget::on_pushButton_clicked()
{
    QString context = ui->lineEdit->text();
    QNetworkDatagram request(context.toUtf8(), QHostAddress(SERVER_IP), SERVER_PORT);
    socket->writeDatagram(request);
    // QListWidgetItem *item= new QListWidgetItem(context);
    // 不会内存泄漏因为 item 给到 ListWidget 进行了管理当父类销毁子类也会消亡,但程序运行的内存会越来越大!
    ui->listWidget->addItem("客户端说:" + context);
    ui->lineEdit->clear();
}

void Widget::processResponse()
{
    const QNetworkDatagram& packet = socket->receiveDatagram();
    QString s = QString::fromUtf8(packet.data());
    ui->listWidget->addItem("服务器说" + s);
}

Qt UDP 总结

常用内容:

  1. QNetworkDatagram 数据报类
    1. 作为发送和接收的数据报类,发送和接收的都是这个类型
    2. 发送时构造的就是传递 ip 和 port 指定目的地以及数据
    3. 接收时则是直接通过 socket 读取到该类然后将 QByteArray 的二进制转换成 QString 即可(QString::fromUtf8 不过也可以直接接收因为 QByteArray 和 utf8 本质都是二进制)
  2. QUdpSocket 文件类:一个类在客户端和服务端都使用到了
    1. 通过这个类完成对消息的读写操作,并通过 QNetWorkDatagram 完成消息传递
    2. 服务端中还需要通过 socket 绑定 bind 服务器的 ip 和 port 信息
    3. 客户端则是自己通过 QNetWorkDatagram 指定发送服务端信息即可
  3. &QUdpSocket::readyRead 消息准备就绪的信号
    1. 通过这个信号连接槽函数,完成对 UDP 数据的处理
    2. 注意一定要先连接槽函数在绑定端口号
  4. .pro 中要添加 network

2. TcpSocket

其中 Tcp 和 Udp 的区别这里就不具体提及了~ 核心 API 概览,核心类是两个: QTcpServer 和 QTcpSocket 分别作用于服务端和客户端(其中 Server 自然就是服务端使用,Socket 则是客户端使用)

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

名称类型说明对标原生 API
listen(const QHostAddress&, quint16 port)方法绑定指定的地址和端口号,并开始监听。bind 和 listen
nextPendingConnection()方法从系统中获取到一个已经建立好的 tcp 连接。返回一个 QTcpSocket,表示这个客户端的连接。通过这个 socket 对象完成和客户端之间的通信。accept
newConnection信号有新的客户端建立连接好之后触发无 (但是类似于 IO 多路复用中的通知机制)

QTcpSocket 用于客户端和服务器之间的数据交互。(有点类似 Udp 中的 QNetworkDatagram)

名称类型说明对标原生 API
readAll()方法读取当前接收缓冲区中的所有数据。返回 QByteArray 对象。read
write(const QByteArray& )方法把数据写入 socket 中。write
deleteLater方法暂时把 socket 对象标记为无效。Qt 会在下个事件循环中析构释放该对象。无 (但是类似于'半自动化的垃圾回收')
peerAddress方法获取对端客户端的 ip(通过 toString 查看 ip 的字符串)无
peerPort方法获取对端客户端 port无
readyRead信号有数据到达并准备就绪时触发。无 (但是类似于 IO 多路复用的通知机制)
disconnected信号连接断开时触发。无 (但是类似于 IO 多路复用的通知机制)

实操 Demo

Tcp 回显服务器
  1. 创建 Widget 项目(.pro 中添加 network)
  2. ui 文件中拖拽一个 ListWidget
  3. 添加 QTcpServer 类成员变量句柄
  4. 构造函数中:
    1. 修改窗口标题 服务器
    2. 创建 QTcpServer 的实例,并挂在对象树上
    3. 创建 newConnection 信号的信号槽 processConnection
      1. 通过 nextPendingConnection 获取一个客户端的 socket(clientSocket 变量)
        1. 然后通过 clientSocket 获取:ip 地址和端口(peerAddress、peerPort 对端客户端的 ip 和端口)
        2. 其中端口需要转换成整数使用 QString::number + '客户端上线' 的信息
        3. 并且在 ListWidget 中添加子项
      2. 处理客户端发来请求的情况,通过 readyRead 信号,lambda 表达式:
        1. 获取客户端的消息 readAll,获取 request 请求报文
        2. 根据请求处理响应 通过 process 函数完成:内部应该是业务逻辑,但此处只是回显,所以直接返回请求的 QString 即可,得到 response 请求报文
        3. 把响应返回给客户端(write)
        4. 把上述内容记录到日志中(这里就打印即可),日志信息:客户端 ip、端口(改成整形)、request、response
      3. 处理客户端断开连接的情况
        1. 通过 disconnect 信号,lambda 表达式:
        2. 把断开连接的信息通过日志显示:客户端 ip + 端口 + 客户端已下线
        3. 存放到 ListWidget 中
        4. 手动释放 ClientSocket(这个东西是可能存在 N 个的,随着客户端越来越多不释放,积累起来就会导致 内存泄漏和文件描述符泄露)
        5. delete clientSocket(这样写时可以的不过必须要保证这是最后一步,因为这个 lambda 中都是围绕着 clientSocket 来进行的,并且也可能因为一些如异常或者 return 导致没有 delete)所以这里还是不建议使用的
        6. 而是通过调用 clientSocket 的 deleteLater(他不是立即销毁的,而是在下一轮事件循环中,再进行上述销毁操作)
    4. 绑定并监听端口号
      1. 一定要确保先写好处如何理连接,处理请求再绑定监听
      2. listen(监听接收任意 ip,端口)
      3. 可能失败所以接收 listen 返回值进行判断 若失败则弹出对话框
      4. QMessageBox::critical this 服务器启动失败 tcpSerccer 的方法 errorString 显示错误原因

上述代码其实不够严谨,作为回显服务器是已经够了的 实际使用 TCP 的过程中,TCP 是面向字节流的,一个完整的请求,可能会分成多断字节数组进行传输 虽然 TCP 已经帮我们处理了很多棘手的问题了,但是 TCP 本身不负责区分,从哪里到哪里是一个完整的应用层数据报 (粘包问题) 更严谨的做法,应该是每次收到的数据都给放到一个大的字节数组缓冲区中,并且提前约定好应用层协议的格式 (分隔符?长度?其他办法?) 再按照协议格式对缓冲区数据进行更细致的解析处理~~ (当前不打算写这么复杂了)

主要源码:

#include "widget.h"
#include "ui_widget.h"

Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    setWindowTitle("服务器");
    server = new QTcpServer(parent);
    connect(server, &QTcpServer::newConnection, this, &Widget::processConnection); // 处理新链接
    // 注意监听前一定要先把上述内容先绑定了!
    bool ret = server->listen(QHostAddress::Any, 8080); // 失败会返回 false 所以进行接收处理
    if(!ret){
        QMessageBox::critical(this, "服务器启动失败", server->errorString()); // 弹出消息框
        return;
    }
}

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

QString Widget::process(QString& requset) // 主要业务逻辑
{
    return requset; // 这里只是最简单的回显服务器所以就直接返回了
}

void Widget::processConnection()
{
    QTcpSocket *clientSocket = server->nextPendingConnection(); // 从系统中获取一个已经建立好的 tcp 连接
    QHostAddress add = clientSocket->peerAddress(); // ip:add.toString();通过转换成 String 就能看到 ip
    quint16 port = clientSocket->peerPort(); // 端口
    ui->listWidget->addItem("[" + add.toString() + ":" + port + "]:" + "客户端已上线");
    
    // 处理客户端发来的请求(同样的 readyRead 信号)
    connect(clientSocket, &QTcpSocket::readyRead, this, [=](){
        // 注意 QT 信号槽中使用 lambda 时不要引用(&)获取而是值(=)获取 注意!!!!
        QString request = clientSocket->readAll(); // 获取用户的 request 数据报
        // 处理请求~~
        const QString& response = process(request);
        clientSocket->write(response.toUtf8());
        QString log = "[" + add.toString() + ":" + QString::number(port) + "] req:" + request + ",res:" + response;
        ui->listWidget->addItem(log);
    });
    
    // 处理客户端断开(disconnected 信号)
    connect(clientSocket, &QTcpSocket::disconnected, this, [=](){
        QString log = "[" + add.toString() + ":" + port + "]已下线";
        qDebug() << log; // 日志打印
        ui->listWidget->addItem(log);
        // 注意一定要释放该 clientSocket,他本质就是文件描述符
        // delete clientSocket; 这个容易出现问题:立即删除对象:立即释放内存、可能正在处理信号槽:如果还有未处理的信号,会导致崩溃、可能引发二次删除:如果有父对象,父对象可能再次删除它、线程不安全:如果在事件循环中调用,可能导致不可预测行为
        clientSocket->deleteLater(); // 他不是立即销毁的,而是在下一轮事件循环中,再进行上述销毁操作;保证信号槽安全:所有挂起的事件和信号都会先被处理
    });
}
Tcp 客户端的实现
  1. 创建新的 Widget 项目
  2. 创建 QTcpSocket 对象 (socket)
  3. 构造函数中:
    1. 设置窗口
    2. 创建 socket 的实例(并指定父对象)
    3. 和服务器建立连接 connectToHost(服务器 ip、端口)开始三次握手(但又不同于原生 api 的 connect 是阻塞的)
    4. 通过 readyRead 信号 的 信号槽,处理响应(直接使用 lambda):
      1. 读取响应内容 response
      2. 把响应内容添加到界面上(ListWidget->addItem)
    5. 等待连接建立的结果,确定是否连接成功 waitForConnected
      1. 同样进行判断 连接是否成功的返回值,若失败则 弹出致命错误对话框
      2. '服务器连接出错';socket 的方法 errorString;并直接终止进程
  4. 给按钮添加点击的槽函数:
    1. 获取输入框内容
    2. 发送输入框内容给服务器注意转成 QByteArray
    3. 把消息显示到界面上 客户端说:text
    4. 清空输入框内容

主要源码:

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

Widget::Widget(QWidget *parent) : QWidget(parent), ui(new Ui::Widget)
{
    ui->setupUi(this);
    socket = new QTcpSocket(parent);
    socket->connectToHost("127.0.0.1", 8080); // 处理返回的消息
    connect(socket, &QTcpSocket::readyRead, this, [=](){
        QString response = socket->readAll();
        ui->listWidget->addItem("服务端说:" + response);
    });
    // 处理连接失败
    if(!socket->waitForConnected()){ 
        QMessageBox::critical(this, "client 连接服务器失败", socket->errorString());
        exit(0); // 结束进程
    }
}

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

void Widget::on_pushButton_clicked()
{
    QString request = ui->lineEdit->text();
    socket->write(request.toUtf8());
    ui->listWidget->addItem("客户端说:" + request); // 最后清空 LineEdit
    ui->lineEdit->clear();
}

之前在学习 Linux 时写的 TCP 的回显服务器的时候,遇到了一个问题:多个客户端同时访问的时候,就只有一个生效 后来引入了多线程,每个客户端安排一个单独的线程,问题才得到改善 之前写的那个程序,之所以出现上述问题,和 TCP 和 多线程 都没啥关系…从来没有说法说 TCP 服务器必须使用多线程编写! 之前存在这个问题的本质原因,是写双重循环,里层循环没有及时结束,导致外层循环不能快速的第二次调用到 accept,导致第二个客户端无法进行处理了。 引入多线程,本质上就是把双重循环,化简成两个独立的循环 咱们 Qt 的服务器程序中,其实一个循环都没写,是通过 Qt 内置的信号槽来驱动的

Qt Tcp 总结

  1. 服务器中 QTcpServer::newConnection 处理新链接的信号,注意需要连接从而完成新链接新用户的消息处理和断开连接处理
  2. nextPendingConnection() 从就绪的连接中获取一个,从而拿到一个客户端(QTcpSocket *)
  3. QTcpSocket 常用的函数:
    1. peerAddress(获取地址)、peerPort(端口)
    2. readAll(获取 socket 中的数据)
    3. write(通过 socket 发送消息数据:客户端需要设置 3 点、而服务端中它拿到的 socket 是已经确定好客户端的),socket 本质其实就是 {ip:port} 找到另外一方
    4. connectToHost(客户端中使用:确定连接的服务端,从而确定消息发送位置)
  4. QTcpSocket::readyRead,同前面客户端消息就绪的信号
  5. 注意 QT 信号槽中使用 lambda 时不要引用(&)获取而是值(=)获取 注意!!!!
  6. QTcpSocket::disconnected 用户断开时会触发的信号

总之:

  1. TcpServer 是服务器用于获取客户端以及处理客户端新链接、新消息、断开的类
  2. TcpSocket 是一个中间件完成消息的传递,服务端可以通过 nextPendingConnection 获取一个连接(用户客户端),而客户端则是直接创建一个 TcpSocket 并且设置要发送的服务端

3. HTTP Client

Qt 中也是提供了 Http 的客户端,Http 协议本质上也就是基于 TCP 协议实现的,实现一个 HTTP 客户端/服务器,本质上也是基于 Tcp Socket 进行封装的

核心 API 关键类主要是三个:

  1. QNetworkAccessManager
  2. QNetworkRequest
  3. 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::KnownHeaders header, const QVariant &value)设置请求头。

其中的 QVariant 本质 可以理解成 void*

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

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

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

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

实操 Demo Http 客户端

上面的框框并不是 ListWidget 了而是 QPlainTextEdit,因为它返回的大概率是一个 html(其中不使用 QTextEdit 是因为他会自动渲染 HTML)

  1. 同样的.pro中添加 network 标识
  2. 添加 QNetworkAccessManger 成员变量(client)
  3. 构造函数:
    1. 实例话添加的成员变量,指定父元素(对象树)
  4. 给按钮添加点击的槽函数:
    1. 获取输入框中的 url(QUrl 对象获取 QString 能自动转换成 QUrl)
    2. 通过 client 调用 get 方法并传递 url(post 请求的话需要添加 body)
    3. 构造一个 HTTP 请求对象 requset(传入 url),会返回一个 QNetworkReply 响应对象
    4. 但同样注意的是 get 只负责发送请求,并不会阻塞等待,那么解决方法:response 内部有一个 finished 信号,当响应返回时就会触发这个信号
    5. 使用 connect 连接并直接通过 lambda 简单实现槽函数即可
    6. 所以通过 response 的 finished 信号 的槽函数进行处理:
      1. 判断 response 的 error 若等于 NoError 则代表响应正确获取:
      2. 通过 readAll 读取 response 的 html 数据(QString)
      3. 并设置到 PlainText 中
      4. 若失败,则将错误信息(使用 errorString)设置到 PalinText 中
      5. 最后同样调用 response 的 deleteLater,让其在该轮事件循环结束后释放

– 注意最后这里访问的是 http 的而非 https

实际开发中,HTTP Client 获取到的数据,也不一定非得是 HTML,更大的可能性是客户端开发和服务器开发约定好交互的数据格式,按照约定的格式,客户端拿到之后,进行解析,并显示到界面上。

源码:

#include "widget.h"
#include "ui_widget.h"

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

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

void Widget::on_pushButton_clicked()
{
    QUrl url = ui->lineEdit->text();
    QNetworkRequest request(url); // 一个 http 请求
    QNetworkReply *response = client->get(request); // 其中这里并不会阻塞,所以需要通过一个信号来处理后续返回
    // 这里的 response QNetworkReply 内部有的信号 finished 当触发时代表响应了
    connect(response, &QNetworkReply::finished, this, [=](){
        if(response->error() == QNetworkReply::NoError){ // 若相等则代表没有错误
            QString req = response->readAll();
            ui->plainTextEdit->setPlainText(req);
        } else {
            ui->plainTextEdit->setPlainText(response->errorString());
        }
        response->deleteLater(); // 下一个事件循环时销毁
    });
}

Qt Http 总结

  1. QNetworkRequest 构造一个 http请求,初始化填入 url 代表路径
  2. 这里 url 需要的类型是 QUrl 但也很好转换 QString 能直接转换
  3. QNetworkAccessManager 中的 get/post(本质就是 http 的 method)函数进行发送 请求 和并且接收响应 QNetworkReply,但需要注意的是响应处不是阻塞的
  4. 所以需要设置一个信号槽,信号是 QNetworkReply::finished 当数据到达时就会触发
  5. 当 QNetworkReply 的 error 函数和 QNetworkReply::NoError 相等就代表没有问题此时就能在继续处理 http 响应了

目录

  1. C++ Qt 网络编程:QUdpSocket、QTcpSocket 与 Http 实战
  2. 绪论
  3. 1. QUdpSocket
  4. 核心 API 概览
  5. 实操 Demo
  6. UDP 回显服务器
  7. UDP 回显客户端 (UDPClient)
  8. Qt UDP 总结
  9. 2. TcpSocket
  10. 实操 Demo
  11. Tcp 回显服务器
  12. Tcp 客户端的实现
  13. Qt Tcp 总结
  14. 3. HTTP Client
  15. 实操 Demo Http 客户端
  16. Qt Http 总结
  • 💰 8折买阿里云服务器限时8折了解详情
  • Magick API 一键接入全球大模型注册送1000万token查看
  • 🤖 一键搭建Deepseek满血版了解详情
  • 一键打造专属AI 智能体了解详情
极客日志微信公众号二维码

微信扫一扫,关注极客日志

微信公众号「极客日志V2」,在微信中扫描左侧二维码关注。展示文案:极客日志V2 zeeklog

更多推荐文章

查看全部
  • 本地文件上传至服务器的常用方法与工具
  • ESP-SR 模型选型指南:如何为你的项目选择最佳语音模型
  • Java Swing 实现的个人所得税计算模拟器源码分享
  • 面壁智能 ChatDev:AI 智能体协作开发平台技术解析
  • 无人机电源管理系统:升压降压电路设计
  • MBA 教务管理实战:订单支付后自动转化学籍与课时卡
  • Quilter:基于物理驱动的 AI 电路板设计工具
  • EhViewer:安卓开源漫画工具安装与使用教程
  • B 站直播弹幕场控机器人使用指南
  • 2026 年 3 月全球大模型全景:国产登顶、百万上下文与智能体爆发
  • Flutter 三方库 langchain_google 的鸿蒙化适配指南
  • Neeshck-Z-lmage_LYX_v2 本地 AI 绘画工具搭建指南
  • 医疗 AI 轻量化部署方案梳理与优化路径
  • Linux 系统远程连接 Windows 桌面配置方法
  • DeepSeek-R1 大模型基于 MS-Swift 框架的部署、推理与微调实践
  • 大模型 RLHF 流程详解与代码实现
  • 通义万相 2.1 实战:AIGC 内容生成与多模态应用指南
  • 利用 URI Scheme 从 Web 页面启动本地 C++ 客户端的实现方案
  • AIGC 镜头控制教程:Next Scene Qwen Image LoRA 实现视角变换
  • 主流大模型架构全景:GPT、LLaMA、DeepSeek 与 Qwen 深度对比

相关免费在线工具

  • Base64 字符串编码/解码

    将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online

  • Base64 文件转换器

    将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online

  • Markdown转HTML

    将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online

  • HTML转Markdown

    将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online

  • JSON 压缩

    通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online

  • JSON美化和格式化

    将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online