QT_HTTP协议编程
http超文本传输协议,是一个简单的请求响应协议,它通常运行在TCP之上,作用规定WWW服务器与浏览器之间信息传递规范,是二者共同遵守的协议。
http工作原理:http是基于客户/服务器模式,且面向连接的,http事务处理流程
- 客户与服务器建立连接
- 客户想服务器提出请求
- 服务器接受请求,并根据请求返回相应的文件作为应答
- 客户与服务器关闭连接
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/html、application/json。 - Content-Length:正文的字节数。
- Connection :
keep-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")));
}

运行说明(补充)
- 点击「获取数据」按钮,程序会异步向
http://www.baidu.com发起 GET 请求。 - 请求发出后,
label显示「资源正在下载中,请耐心等待...」。 - 当
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
运行与测试方式(补充)
- 编译运行程序,控制台输出「正常启动:web服务器端口:8080」。
- 打开任意浏览器,访问
http://localhost:8080(或本机 IP 加端口)。 - 页面显示「客户端已经连接到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:静态局部变量只初始化一次,之后每次进入函数都保留上次的值,用于累计访问次数。