QT_HTTP协议编程

QT_HTTP协议编程

http超文本传输协议,是一个简单的请求响应协议,它通常运行在TCP之上,作用规定WWW服务器与浏览器之间信息传递规范,是二者共同遵守的协议。

http工作原理:http是基于客户/服务器模式,且面向连接的,http事务处理流程

  1. 客户与服务器建立连接
  2. 客户想服务器提出请求
  3. 服务器接受请求,并根据请求返回相应的文件作为应答
  4. 客户与服务器关闭连接

HTTP 报文结构(补充)

HTTP 协议在客户端与服务器之间传递的数据统称为「报文」,分为请求报文响应报文两大类。二者结构相似,都由四部分组成:

1. 请求报文(Request)

复制代码
请求行(方法 + URL + 版本)
请求头(若干 key: value 键值对)
空行(\r\n\r\n,用于分隔头部与正文)
请求体(可选,如 POST 提交的数据

示例(GET 请求):

http 复制代码
GET /index.html HTTP/1.1
Host: www.baidu.com
User-Agent: Mozilla/5.0
Accept: text/html
Connection: keep-alive

2. 响应报文(Response)

复制代码
状态行(版本 + 状态码 + 原因短语)
响应头
空行
响应体(服务器返回的实际内容)

示例(对应项目二的服务器返回):

http 复制代码
HTTP/1.1 200 OK
Content-Type: text/html
Connection: close
Refresh: 1

<!DOCTYPE><html>...</html>

3. 常用请求方法(Method)

方法 含义 是否带请求体
GET 请求获取指定资源(数据附在 URL 上)
POST 向服务器提交数据(数据在请求体中)
PUT 上传/更新指定资源
DELETE 删除指定资源
HEAD 只获取响应头(不返回正文)

4. 常见状态码(Status Code)

状态码 含义
200 OK,请求成功(项目二中用到)
301/302 重定向
400 请求有误(语法错误)
403 禁止访问
404 资源未找到
500 服务器内部错误

5. 常见请求头/响应头(补充)

  • Host:请求的目标主机名(HTTP/1.1 必带)。
  • User-Agent:标识客户端类型(浏览器/程序)。
  • Content-Type :声明正文数据的类型,如 text/htmlapplication/json
  • Content-Length:正文的字节数。
  • Connectionkeep-alive(保持连接)或 close(关闭连接)。
  • Refresh:让浏览器在指定秒数后自动刷新页面(项目二中用到)。

相关API

Qt 中进行 HTTP 编程的核心类是 QNetworkAccessManager,它负责发送网络请求并接收响应,封装了底层的 TCP 细节。常用的相关类如下:

1. QNetworkAccessManager(网络访问管理器)

HTTP 编程的「总调度」,一个程序中通常只需创建一个实例(可复用)。主要方法:

  • get(请求):发送一次 GET 请求,返回一个 QNetworkReply 对象。
  • post(请求, 数据):发送一次 POST 请求。
  • put(请求, 数据):上传数据。
  • deleteResource(请求):发送 DELETE 请求。

注意:以上方法都是异步 的,调用后会立刻返回,真正的响应通过信号 finished(QNetworkReply*) 通知。

2. QNetworkRequest(网络请求对象)

用来描述一次「请求」的详细信息(目标地址、请求头等)。通常结合 QUrl 使用:

cpp 复制代码
QNetworkRequest req(QUrl("http://www.baidu.com"));
req.setHeader(QNetworkRequest::UserAgentHeader, "MyQtApp/1.0"); // 设置请求头

3. QNetworkReply(网络应答对象)

继承自 QIODevice,表示一次请求的「响应」。每次请求都会生成一个新的 reply 对象,用完需要释放。常用成员:

  • readAll():读取服务器返回的全部数据(QByteArray)。
  • error():获取错误信息。
  • attribute(QNetworkRequest::HttpStatusCodeAttribute):读取 HTTP 状态码。

4. QUrl(统一资源定位符)

用于解析、构造 URL,是网络请求的地址载体。

5. 关键信号与槽

  • QNetworkAccessManager::finished(QNetworkReply*):当一个请求完成后触发,无论成功或失败都会发出。
  • 典型用法:在槽函数里 readAll() 读取数据 → 处理 → reply->deleteLater() 释放。

HTTP项目实战一

项目简介(补充)

本实战使用 QNetworkAccessManager 实现一个 GET 请求客户端:点击按钮后向指定 URL 发起请求,把服务器返回的内容显示到界面上,并提示下载状态。

工程配置(补充):.pro 文件

Qt 5 及以前版本使用 QT += network,Qt 6 无需额外模块(网络模块已默认包含),但加上也不报错。示例如下:

qmake 复制代码
QT       += core gui network

greaterThan(QT_MAJOR_VERSION, 4): QT += widgets

TARGET = HttpGetDemo
TEMPLATE = app

SOURCES += \
        main.cpp \
        mainwindow.cpp

HEADERS += \
        mainwindow.h

FORMS += \
        mainwindow.ui

关键点:必须添加 network 模块,否则会在链接阶段报 QNetworkAccessManager 未定义等错误。

界面布局(补充)

在 UI 设计器中拖入以下控件即可(对应代码中的对象名):

  • 一个 QPushButton,objectName 改为 getDataBtn,文字如「获取数据」。
  • 一个 QLabel,objectName 为 label,用于显示下载状态。
  • 一个 QTextBrowser(或 QPlainTextEdit),objectName 为 textBrowser,用于显示返回内容。

代码文件

mainwindow.h

cpp 复制代码
#ifndef MAINWINDOW_H
#define MAINWINDOW_H

#include <QMainWindow>
#include <QtNetwork> // 提供编程Tcp/IP客户端和服务器的类
#include <QUrl> // 提供接口使用URLs
class QNetworkAccessManager;
class QNetworkReply; // 此类是QIODevice的子类(补充:原注释 "QIDevice" 应为 QIODevice)


QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE

class MainWindow : public QMainWindow
{
    Q_OBJECT

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

private:
    Ui::MainWindow *ui;
    QNetworkAccessManager *mgr;
public slots:
    void replayFinishedFunc(QNetworkReply *); //响应(补充:函数名语义应为 replyFinishedFunc,与信号 finished 对应)
private slots:
    void on_getDataBtn_clicked();
};
#endif // MAINWINDOW_H

mainwindow.cpp

cpp 复制代码
#include "mainwindow.h"
#include "ui_mainwindow.h"

MainWindow::MainWindow(QWidget *parent)
    : QMainWindow(parent)
    , ui(new Ui::MainWindow)
{
    ui->setupUi(this);
    mgr = new QNetworkAccessManager(this);
    connect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(replayFinishedFunc(QNetworkReply*)));
}

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

void MainWindow::replayFinishedFunc(QNetworkReply *reply){
    QString strall = reply->readAll(); //读取数据
    ui->textBrowser->setText(strall); // 显示数据
    ui->label->setText("下载完成");
    reply->deleteLater();
}

// ===== 以下为补充的优化点与扩展代码 =====

// 补充 1:错误处理。上面的代码无论请求成功与否都显示「下载完成」,
// 实际项目中建议根据 reply->error() 判断,避免访问失败时误导用户。
// 可改进为:
/*
void MainWindow::replayFinishedFunc(QNetworkReply *reply){
    if (reply->error() == QNetworkReply::NoError) {
        QString strall = reply->readAll();
        ui->textBrowser->setText(strall);
        ui->label->setText("下载完成");
    } else {
        ui->label->setText("下载失败:" + reply->errorString());
    }
    reply->deleteLater();
}
*/

// 补充 2:获取 HTTP 状态码(在 readAll 之前读取)。
/*
QVariant statusCode = reply->attribute(QNetworkRequest::HttpStatusCodeAttribute);
if (statusCode.isValid()) {
    qDebug() << "状态码:" << statusCode.toInt();
}
*/

void MainWindow::on_getDataBtn_clicked()
{
    ui->label->setText("资源正在下载中,请耐心等待...");
    mgr->get(QNetworkRequest(QUrl("http://www.baidu.com")));
}

运行说明(补充)

  1. 点击「获取数据」按钮,程序会异步向 http://www.baidu.com 发起 GET 请求。
  2. 请求发出后,label 显示「资源正在下载中,请耐心等待...」。
  3. finished 信号触发后,replayFinishedFunc 槽函数把返回的网页源码显示在 textBrowser 中,并把 label 改为「下载完成」。

关键点小结(补充)

  • QNetworkAccessManager 只需 new 一次,在窗口生命周期内复用即可,父对象设为 this 会自动释放。
  • mgr->get()异步 操作,不会阻塞界面;真正的结果通过 finished 信号异步返回。
  • 槽函数末尾必须调用 reply->deleteLater() 释放 reply 对象,避免内存泄漏。
  • 连接信号槽时,新式写法 &QNetworkAccessManager::finished 比字符串 SIGNAL/SLOT 更安全(可被编译器检查)。

HTTP项目实战二

项目简介(补充)

本实战不依赖 QNetworkAccessManager,而是直接基于 QTcpServer / QTcpSocket 手写一个极简 HTTP 服务器:监听 8080 端口,当浏览器访问时返回一段带自动刷新(每秒 +1)计数的 HTML 页面。

通过这个例子,可以直观看到 HTTP 协议「底层就是 TCP + 文本报文」的本质------响应报文是直接拼字符串再 write 出去的。

工程配置(补充):.pro 文件

因为是控制台程序(QCoreApplication),无需 widgets 模块:

qmake 复制代码
QT += core network
QT -= gui

CONFIG += console
CONFIG -= app_bundle

TARGET = MiniHttpServer
TEMPLATE = app

SOURCES += \
        main.cpp \
        myhttpserver.cpp

HEADERS += \
        myhttpserver.h

运行与测试方式(补充)

  1. 编译运行程序,控制台输出「正常启动:web服务器端口:8080」。
  2. 打开任意浏览器,访问 http://localhost:8080(或本机 IP 加端口)。
  3. 页面显示「客户端已经连接到httpServer服务器,秒数为:N」,且因响应头含 Refresh: 1,页面每秒自动刷新、数字每秒递增。

代码文件

main.cpp

cpp 复制代码
/**
 * @file    main.cpp
 * @brief   程序入口文件
 *
 * 这是一个基于 Qt 控制台(无 GUI)的极简 HTTP Web 服务器示例程序。
 * 程序启动后会创建 myHttpServer 对象,监听 8080 端口,
 * 当浏览器访问 http://localhost:8080 时,返回一段显示访问计数的 HTML 页面。
 */

#include <QCoreApplication>    // 控制台应用程序类(不需要 GUI)
#include "myhttpserver.h"      // 自定义的 HTTP 服务器类

// ===== 补充说明 =====
// 与项目一不同,这里不使用 QNetworkAccessManager,
// 而是用 QTcpServer 直接监听端口、手写 HTTP 响应报文。
// 本质:HTTP 请求/响应就是基于 TCP 传输的文本格式报文。

/**
 * @brief 程序入口函数
 * @param argc 命令行参数个数
 * @param argv 命令行参数数组
 * @return 程序退出码
 */
int main(int argc, char *argv[])
{
    // 创建 Qt 控制台应用程序对象
    // 该对象负责管理程序的事件循环等基础功能
    QCoreApplication a(argc, argv);

    // 创建 HTTP 服务器对象
    // 构造函数中会自动创建 QTcpServer 并监听 8080 端口
    myHttpServer server;

    // 进入 Qt 事件循环,等待并处理各类事件(如新的网络连接)
    // 程序会一直运行,直到收到退出信号
    return a.exec();
}

myhttpserver.h

cpp 复制代码
/**
 * @file    myhttpserver.h
 * @brief   myHttpServer 类的声明文件
 *
 * 该类基于 Qt 的 QTcpServer / QTcpSocket 实现了一个极简的 HTTP Web 服务器:
 *  - 监听本机所有网卡的 8080 端口
 *  - 当有浏览器(客户端)连接进来时,返回一段简单的 HTML 页面
 *  - 页面内容包含一个每秒自增的访问计数
 *
 * 本文件只负责类的声明,具体实现见 myhttpserver.cpp。
 */

#ifndef MYHTTPSERVER_H
#define MYHTTPSERVER_H

#include <QObject>      // 所有 Qt 对象的基类,提供信号槽机制
#include <QTcpServer>   // TCP 服务器类,用于监听端口、接受客户端连接
#include <QTcpSocket>   // TCP 套接字类,用于与单个客户端进行数据收发

/**
 * @class myHttpServer
 * @brief 一个极简的 HTTP Web 服务器类
 *
 * 继承自 QObject,以便使用 Qt 的信号与槽机制。
 * 主要工作流程:
 *  1. 构造函数中创建 QTcpServer 并监听 8080 端口
 *  2. 有新连接到来时触发 newConnection 信号
 *  3. 在 connection() 槽函数中读取请求、返回 HTML 响应
 */
class myHttpServer : public QObject
{
    Q_OBJECT    // 使用信号槽等 Qt 元对象特性所必需的宏

public:
    /**
     * @brief 构造函数
     * @param parent 父对象指针,默认为 nullptr
     *
     * 内部会创建 QTcpServer 并开始监听 8080 端口。
     */
    explicit myHttpServer(QObject *parent = nullptr);

    /**
     * @brief 析构函数
     * 负责释放当前连接的 socket 等资源。
     */
    ~myHttpServer();

public:
    /**
     * @brief 当前连接的客户端套接字指针
     *
     * 每次有新客户端连接时,都会在 connection() 中被重新赋值,
     * 指向最新的那个客户端连接。
     */
    QTcpSocket *socket;

private:
    /**
     * @brief TCP 服务器对象指针
     * 负责监听端口,并在有客户端连接时发出 newConnection 信号。
     */
    QTcpServer *ser;

    /**
     * @brief 获取当前套接字中可读的字节数
     * @return 可读字节数
     *
     * 注意:该函数当前只有声明、没有实现,属于遗留/占位声明。
     */
    qint64 bytesAvailable() const;

public slots:
    /**
     * @brief 处理新客户端连接的槽函数
     *
     * 当 QTcpServer 发出 newConnection 信号时被自动调用。
     * 负责读取浏览器发来的请求数据,并向浏览器返回一段 HTML 页面。
     */
    void connection();
};

#endif // MYHTTPSERVER_H

myhttpserver.cpp

cpp 复制代码
/**
 * @file    myhttpserver.cpp
 * @brief   myHttpServer 类的实现文件
 *
 * 实现了一个极简的 HTTP Web 服务器,工作流程如下:
 *  1. 构造函数:创建 QTcpServer,监听 8080 端口
 *  2. connection():处理新连接,读取请求并返回 HTML 页面
 */

#include "myhttpserver.h"
#include <QDebug>   // 用于在控制台输出调试/运行信息

/**
 * @brief 构造函数
 * @param parent 父对象指针,默认为 nullptr
 *
 * 初始化流程:
 *  1. 创建 QTcpServer 对象(父对象为 this,Qt 会自动管理其内存)
 *  2. 将 newConnection 信号连接到 connection() 槽函数
 *  3. 调用 listen() 监听本机所有网卡(QHostAddress::Any)的 8080 端口
 *  4. 根据监听是否成功,在控制台输出相应的提示信息
 */
myHttpServer::myHttpServer(QObject *parent) : QObject(parent)
{
    // 创建一个 TCP 服务器对象,this 作为其父对象(便于 Qt 自动释放内存)
    ser = new QTcpServer(this);

    // 信号与槽:
    // 当有新的客户端连接到来时,QTcpServer 会发出 newConnection 信号,
    // 这里将其连接到本类的 connection() 槽函数,以处理该连接
    connect(ser, &QTcpServer::newConnection, this, &myHttpServer::connection);

    // 尝试监听本机所有网卡地址的 8080 端口
    if (!ser->listen(QHostAddress::Any, 8080)) {
        // 监听失败:输出错误信息提示用户检查端口占用或权限问题
        qDebug() << "致命错误,web服务器没有启动,请重新检查" << endl;
    } else {
        // 监听成功:输出正常启动提示
        qDebug() << "正常启动:web服务器端口:8080,等待客户端连接...." << endl;
    }
}

/**
 * @brief 处理新客户端连接的槽函数
 *
 * 当 QTcpServer 发出 newConnection 信号时被调用,执行以下操作:
 *  1. 取出待处理的新连接(QTcpSocket)
 *  2. 等待并读取浏览器发来的 HTTP 请求数据
 *  3. 构造并返回一个简单的 HTTP 响应(HTML 页面)
 *  4. 页面中显示一个每秒自增的访问计数
 */
void myHttpServer::connection()
{
    // 取出下一个待处理的客户端连接,赋值给成员变量 socket
    // 注意:这里直接使用成员变量保存连接,若同时有多个连接会互相覆盖
    socket = ser->nextPendingConnection();

    // 循环等待,直到套接字上有可读数据(每次最多等待 100 毫秒)
    // waitForReadyRead(100) 返回 true 表示有数据可读,返回 false 则继续等待
    while (!(socket->waitForReadyRead(100)));

    // 定义一个缓冲区,用于存放从客户端读取到的请求数据
    char webdata[1000];

    // 从套接字读取最多 1000 字节的请求数据到缓冲区
    // sv 为实际读取到的字节数(当前未使用,属于冗余变量)
    int sv = socket->read(webdata, 1000);

    // 在控制台打印读取到的请求信息,便于调试观察
    qDebug() << "正常运行,从浏览器读取数据信息" << QString(webdata) << endl;

    // ---------- 下面开始构造 HTTP 响应头 ----------
    // 状态行:HTTP/1.1 200 OK,表示请求成功
    socket->write("HTTP/1.1 200 OK\r\n");

    // 响应头:声明返回内容的类型为 HTML 文本
    socket->write("Content-Type:text/html\r\n");

    // 响应头:声明连接在本次响应结束后关闭(Connection: close)
    socket->write("Connection:close\r\n");

    // 响应头:Refresh:1 表示浏览器每隔 1 秒自动刷新一次页面
    socket->write("Refresh:1\r\n\r\n"); // 每秒刷新web浏览器

    // ---------- 下面开始构造 HTML 页面内容 ----------
    // 依次写入 HTML 文档结构:
    //  <!DOCTYPE>    文档类型声明
    //  <html>        HTML 根标签
    //  <header>      头部区域
    //  <meta ...>    设置字符集为 UTF-8,避免中文乱码
    //  <title>       浏览器标签页标题
    //  <body>        页面主体,显示连接提示信息
    socket->write("<!DOCTYPE>"
                  "<html>"
                  "<header>"
                  "<meta http-equiv=\"content-Type\" content=\"text/html;charset=utf-8\"/>"
                  "<title>httpserver</title>"
                  "</header>"
                  "<body>客户端已经连接到httpServer服务器,秒数为:</body>");

    // 定义一个字节数组,用于存放访问计数
    QByteArray bytesary;

    // 声明静态计数器(静态变量只会初始化一次,之后每次调用都会保留上一次的值)
    // 用于统计服务器启动以来的访问次数,并在页面上显示
    static qint16 icount;

    // 将当前计数值写入字节数组,同时 icount 自增 1(先用后加)
    bytesary.setNum(icount++);

    // 把计数值写入响应(追加到 HTML 中 body 之后)
    socket->write(bytesary);

    // 写入 HTML 结束标签
    socket->write("</html>");

    // 立即将缓冲区中的数据发送给客户端
    socket->flush();

    // 当客户端断开连接时,让该 socket 自动删除自身,释放内存
    connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);

    // 主动断开与客户端的连接(配合上面的 Connection: close 响应头)
    socket->disconnectFromHost();
}

/**
 * @brief 析构函数
 *
 * 关闭当前连接的套接字,释放连接资源。
 */
myHttpServer::~myHttpServer()
{
    socket->close();
}

关键点小结

  • HTTP 报文本质 :响应头 + 空行 + 响应体,代码里用多个 socket->write() 拼接而成,其中 \r\n 是换行符,\r\n\r\n 表示「头部结束、正文开始」。
  • Refresh: 1:让浏览器每隔 1 秒自动重新请求一次页面,从而实现「计数每秒自增」的效果。
  • Connection: close :告知浏览器本次响应结束后关闭连接,服务端随后调用 disconnectFromHost() 主动断开。
  • static qint16 icount:静态局部变量只初始化一次,之后每次进入函数都保留上次的值,用于累计访问次数。
相关推荐
码农客栈28 分钟前
Qt 打包为可执行文件发布
qt
楷哥爱开发21 小时前
HTTP代理是什么?性能分析、适用场景与配置教程
网络·网络协议·http
IPdodo_21 小时前
2026年AI 数据采集代理 IP 选型:成功率、并发、轮换与成本评估
前端·网络·人工智能·chrome·python·http·网络调试
鱼很腾apoc21 小时前
【Linux】第13期 详解应用层协议HTTP+Cookie/Session+HTTPS
linux·服务器·c++·学习·http·https
q567315231 天前
Curl 报 CONNECT tunnel failed, response 6xx:排查思路全解
数据库·网络协议·scrapy·http·中间件·http代理
布莱克6051 天前
单例模式详解:什么是单例模式,它能解决哪些问题?
开发语言·qt·单例模式
qq762118221 天前
windows Qt mingw 编译libzip
开发语言·windows·qt
skr爱码士1 天前
10_Qt Designer 与 UI 文件(.ui 原理、uic 编译、动态加载)
c++·qt·ui·客户端
IT爱学堂1 天前
QT原理与源码分析可以学到什么 QT视频课程 QT课程推荐 最新推荐
开发语言·qt