QT_HTTP协议编程
QT_HTTP协议编程
http超文本传输协议,是一个简单的请求响应协议,它通常运行在TCP之上,作用规定WWW服务器与浏览器之间信息传递规范,是二者共同遵守的协议。
http工作原理:http是基于客户/服务器模式,且面向连接的,http事务处理流程
- 客户与服务器建立连接
- 客户想服务器提出请求
- 服务器接受请求,并根据请求返回相应的文件作为应答
- 客户与服务器关闭连接
HTTP 报文结构(补充)
HTTP 协议在客户端与服务器之间传递的数据统称为「报文」,分为请求报文和响应报文两大类。二者结构相似,都由四部分组成:
1. 请求报文(Request)
请求行(方法 + URL + 版本) 请求头(若干 key: value 键值对) 空行(\r\n\r\n,用于分隔头部与正文) 请求体(可选,如 POST 提交的数据示例(GET 请求):
GET /index.html HTTP/1.1 Host: www.baidu.com User-Agent: Mozilla/5.0 Accept: text/html Connection: keep-alive2. 响应报文(Response)
状态行(版本 + 状态码 + 原因短语) 响应头 空行 响应体(服务器返回的实际内容)示例(对应项目二的服务器返回):
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使用:
QNetworkRequestreq(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 无需额外模块(网络模块已默认包含),但加上也不报错。示例如下:
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
#ifndefMAINWINDOW_H#defineMAINWINDOW_H#include<QMainWindow>#include<QtNetwork>// 提供编程Tcp/IP客户端和服务器的类#include<QUrl>// 提供接口使用URLsclassQNetworkAccessManager;classQNetworkReply;// 此类是QIODevice的子类(补充:原注释 “QIDevice” 应为 QIODevice)QT_BEGIN_NAMESPACEnamespaceUi{classMainWindow;}QT_END_NAMESPACEclassMainWindow:publicQMainWindow{Q_OBJECTpublic:MainWindow(QWidget*parent=nullptr);~MainWindow();private:Ui::MainWindow*ui;QNetworkAccessManager*mgr;publicslots:voidreplayFinishedFunc(QNetworkReply*);//响应(补充:函数名语义应为 replyFinishedFunc,与信号 finished 对应)privateslots:voidon_getDataBtn_clicked();};#endif// MAINWINDOW_Hmainwindow.cpp
#include"mainwindow.h"#include"ui_mainwindow.h"MainWindow::MainWindow(QWidget*parent):QMainWindow(parent),ui(newUi::MainWindow){ui->setupUi(this);mgr=newQNetworkAccessManager(this);connect(mgr,SIGNAL(finished(QNetworkReply*)),this,SLOT(replayFinishedFunc(QNetworkReply*)));}MainWindow::~MainWindow(){deleteui;}voidMainWindow::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(); } */voidMainWindow::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模块:
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
/** * @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 程序退出码 */intmain(intargc,char*argv[]){// 创建 Qt 控制台应用程序对象// 该对象负责管理程序的事件循环等基础功能QCoreApplicationa(argc,argv);// 创建 HTTP 服务器对象// 构造函数中会自动创建 QTcpServer 并监听 8080 端口myHttpServer server;// 进入 Qt 事件循环,等待并处理各类事件(如新的网络连接)// 程序会一直运行,直到收到退出信号returna.exec();}myhttpserver.h
/** * @file myhttpserver.h * @brief myHttpServer 类的声明文件 * * 该类基于 Qt 的 QTcpServer / QTcpSocket 实现了一个极简的 HTTP Web 服务器: * - 监听本机所有网卡的 8080 端口 * - 当有浏览器(客户端)连接进来时,返回一段简单的 HTML 页面 * - 页面内容包含一个每秒自增的访问计数 * * 本文件只负责类的声明,具体实现见 myhttpserver.cpp。 */#ifndefMYHTTPSERVER_H#defineMYHTTPSERVER_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 响应 */classmyHttpServer:publicQObject{Q_OBJECT// 使用信号槽等 Qt 元对象特性所必需的宏public:/** * @brief 构造函数 * @param parent 父对象指针,默认为 nullptr * * 内部会创建 QTcpServer 并开始监听 8080 端口。 */explicitmyHttpServer(QObject*parent=nullptr);/** * @brief 析构函数 * 负责释放当前连接的 socket 等资源。 */~myHttpServer();public:/** * @brief 当前连接的客户端套接字指针 * * 每次有新客户端连接时,都会在 connection() 中被重新赋值, * 指向最新的那个客户端连接。 */QTcpSocket*socket;private:/** * @brief TCP 服务器对象指针 * 负责监听端口,并在有客户端连接时发出 newConnection 信号。 */QTcpServer*ser;/** * @brief 获取当前套接字中可读的字节数 * @return 可读字节数 * * 注意:该函数当前只有声明、没有实现,属于遗留/占位声明。 */qint64bytesAvailable()const;publicslots:/** * @brief 处理新客户端连接的槽函数 * * 当 QTcpServer 发出 newConnection 信号时被自动调用。 * 负责读取浏览器发来的请求数据,并向浏览器返回一段 HTML 页面。 */voidconnection();};#endif// MYHTTPSERVER_Hmyhttpserver.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=newQTcpServer(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. 页面中显示一个每秒自增的访问计数 */voidmyHttpServer::connection(){// 取出下一个待处理的客户端连接,赋值给成员变量 socket// 注意:这里直接使用成员变量保存连接,若同时有多个连接会互相覆盖socket=ser->nextPendingConnection();// 循环等待,直到套接字上有可读数据(每次最多等待 100 毫秒)// waitForReadyRead(100) 返回 true 表示有数据可读,返回 false 则继续等待while(!(socket->waitForReadyRead(100)));// 定义一个缓冲区,用于存放从客户端读取到的请求数据charwebdata[1000];// 从套接字读取最多 1000 字节的请求数据到缓冲区// sv 为实际读取到的字节数(当前未使用,属于冗余变量)intsv=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;// 声明静态计数器(静态变量只会初始化一次,之后每次调用都会保留上一次的值)// 用于统计服务器启动以来的访问次数,并在页面上显示staticqint16 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:静态局部变量只初始化一次,之后每次进入函数都保留上次的值,用于累计访问次数。
