Qt 快速搭建局域网 HTTP 下载服务(兼容 IE/Chrome/Edge/Firefox)
在工作中,我们有时需要在局域网内提供文件下载功能,例如压缩包或可执行文件。用传统方式可能需要安装 IIS 或 Apache,但如果只是临时调试或快速分享,使用 Qt 自带的网络模块就能轻松实现一个小型 HTTP 服务器,支持点击网页按钮下载文件。本文详细讲解 如何用 Qt 实现局域网文件下载服务,并兼容 IE 浏览器。
一、需求分析
目标如下:
- 用 Qt 快速启动 HTTP 服务,监听局域网端口(如 8080)
- 提供一个网页界面,用户点击按钮即可下载文件
- 支持 IE、Edge、Chrome、Firefox 等浏览器
- 尽量简化部署:双击 EXE 即可运行,不依赖外部 HTML 或 ZIP 文件。
二、问题与挑战
在实现过程中遇到几个问题:
- 工作路径问题
调试启动时,QDir::currentPath() 默认是构建目录,而不是 EXE 所在目录,导致找不到 index.html 或 download.zip。 - IE 浏览器兼容性问题
HTML5 的 download 属性 IE 不支持,导致按钮无法直接下载文件。
局域网访问问题
默认只监听 localhost 或使用防火墙未开放端口,会导致其他设备无法访问。
三、解决方案
- 使用 QCoreApplication::applicationDirPath()
取 EXE 所在目录,保证文件路径不受调试或工作目录影响:
QString baseDir = QCoreApplication::applicationDirPath();
QFile file(baseDir + "/download.zip");
在代码中通过 ":/index.html" 和 ":/download.zip" 访问。 - 服务器返回 Content-Disposition: attachment
为了兼容 IE,文件下载必须通过服务器返回头部控制:
response.append("Content-Type: application/octet-stream\r\n");
response.append("Content-Disposition: attachment; filename="download.zip"\r\n");
IE / Chrome / Firefox / Edge 均支持。 - HTML 按钮兼容写法
不使用 download 属性,直接用按钮触发跳转:
下载 download.zip
IE 点击按钮即可弹出下载对话框。
完整代码如下:
main.cpp
cpp
#include <QCoreApplication> // 核心应用程序类
#include <QTcpServer> // TCP 服务器类
#include <QTcpSocket> // TCP 套接字类
#include <QFile> // 文件读取
#include <QTextStream> // 文本流(可选)
#include <QFileInfo> // 文件信息获取
#include <QDir> // 目录操作
// 自定义 HTTP 服务器类,继承 QTcpServer
class HttpServer : public QTcpServer
{
Q_OBJECT
public:
// 构造函数
HttpServer(QObject *parent = nullptr) : QTcpServer(parent) {}
protected:
// 重写 incomingConnection 函数,处理客户端连接
void incomingConnection(qintptr socketDescriptor) override {
// 创建一个 TCP 套接字,用于和客户端通信
QTcpSocket *socket = new QTcpSocket();
socket->setSocketDescriptor(socketDescriptor);
// 连接 readyRead 信号,当客户端有数据可读时触发
connect(socket, &QTcpSocket::readyRead, [socket]() {
// 读取客户端请求数据
QByteArray request = socket->readAll();
QString requestStr(request);
// 将请求按行分割
QStringList lines = requestStr.split("\r\n");
if (lines.isEmpty()) return; // 防止空请求
// 获取请求的第一行,例如: "GET /index.html HTTP/1.1"
QString firstLine = lines[0];
QStringList parts = firstLine.split(" ");
if (parts.size() < 2) return; // 防止非法请求
QString path = parts[1]; // 请求路径,例如 "/" 或 "/download.zip"
QString baseDir = QCoreApplication::applicationDirPath(); // 获取 EXE 所在目录
// ----------------------- 处理网页请求 -----------------------
if (path == "/" || path == "/index.html") {
// 打开 index.html 文件
QFile htmlFile(baseDir + "/index.html");
QByteArray content;
if (htmlFile.open(QIODevice::ReadOnly)) {
content = htmlFile.readAll();
htmlFile.close();
} else {
// 文件不存在或无法打开,打印调试信息
qDebug() << "当前工作路径:" << QDir::currentPath();
content = "<h1>无法打开网页</h1>";
}
// 构造 HTTP 响应头
QByteArray response;
response.append("HTTP/1.1 200 OK\r\n"); // 状态码 200 OK
response.append("Content-Type: text/html; charset=utf-8\r\n"); // 网页类型
response.append("Content-Length: " + QByteArray::number(content.size()) + "\r\n"); // 内容长度
response.append("\r\n"); // 头部结束
response.append(content); // 网页内容
// 发送响应并断开连接
socket->write(response);
socket->disconnectFromHost();
}
// ----------------------- 处理压缩包下载请求 -----------------------
else if (path == "/download.zip") {
QFile file(baseDir + "/download.zip");
if (!file.exists() || !file.open(QIODevice::ReadOnly)) {
// 文件不存在,返回 404
QByteArray response = "HTTP/1.1 404 Not Found\r\n\r\nFile Not Found";
socket->write(response);
socket->disconnectFromHost();
return;
}
// 读取文件内容
QByteArray fileData = file.readAll();
QFileInfo fi(file);
// 构造 HTTP 响应头,兼容所有浏览器(包括 IE)
QByteArray response;
response.append("HTTP/1.1 200 OK\r\n"); // 状态码
response.append("Content-Type: application/zip\r\n"); // 文件类型
response.append("Content-Disposition: attachment; filename=\"" + fi.fileName().toUtf8() + "\"\r\n"); // 附件下载
response.append("Content-Length: " + QByteArray::number(fileData.size()) + "\r\n"); // 文件大小
response.append("\r\n"); // 头部结束
response.append(fileData); // 文件内容
// 发送响应并断开连接
socket->write(response);
socket->disconnectFromHost();
}
// ----------------------- 处理其他路径 -----------------------
else {
// 返回 404 Not Found
QByteArray response = "HTTP/1.1 404 Not Found\r\n\r\nPath Not Found";
socket->write(response);
socket->disconnectFromHost();
}
});
// 当客户端断开连接时,自动删除 socket 对象,防止内存泄漏
connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
}
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
// 创建 HTTP 服务器对象
HttpServer server;
// 监听局域网任意 IP 的 8080 端口
if (!server.listen(QHostAddress::Any, 8080)) {
qCritical() << "无法启动服务器,端口可能被占用";
return -1;
}
qDebug() << "HTTP 服务已启动,局域网访问:http://<你的IP>:8080/";
// 进入事件循环
return a.exec();
}
index.html
html
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>局域网下载页面</title>
</head>
<body>
<h2>下载压缩包</h2>
<p>点击下面按钮下载压缩包:</p>
<button onclick="window.location.href='download.zip'">下载 download.zip</button>
</body>
</html>
好看的界面如下所示
css
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>局域网下载页面</title>
<style>
body {
margin: 0;
padding: 0;
font-family: "Microsoft YaHei", sans-serif;
height: 100vh;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
background: linear-gradient(135deg, #1f1c2c, #928dab);
color: #fff;
overflow: hidden;
position: relative;
}
h2 {
font-size: 3em;
margin-bottom: 10px;
text-shadow: 2px 2px 10px rgba(0,0,0,0.6);
animation: fadeIn 2s ease-out forwards;
z-index: 2;
position: relative;
}
p {
font-size: 1.2em;
margin-bottom: 40px;
animation: fadeIn 2s ease-out 0.5s forwards;
opacity: 0;
z-index: 2;
position: relative;
}
button {
background: linear-gradient(90deg, #ff512f, #dd2476);
border: none;
border-radius: 50px;
padding: 20px 60px;
color: #fff;
font-size: 1.5em;
cursor: pointer;
box-shadow: 0 10px 20px rgba(0,0,0,0.4);
transition: all 0.3s ease;
position: relative;
overflow: hidden;
animation: fadeIn 2s ease-out 1s forwards;
opacity: 0;
z-index: 2;
}
button:hover {
transform: scale(1.1) rotate(-2deg);
box-shadow: 0 15px 30px rgba(0,0,0,0.5);
}
button:active::after {
content: '';
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
background: rgba(255,255,255,0.2);
border-radius: 50px;
animation: ripple 0.6s ease-out;
}
@keyframes fadeIn {
0% { opacity: 0; transform: translateY(20px); }
100% { opacity: 1; transform: translateY(0); }
}
@keyframes ripple {
0% { transform: scale(0); opacity: 1; }
100% { transform: scale(1.5); opacity: 0; }
}
.particles {
position: absolute;
width: 100%;
height: 100%;
top: 0;
left: 0;
z-index: 1; /* 背景层 */
pointer-events: none; /* 不阻挡点击 */
background-image: radial-gradient(circle, rgba(255,255,255,0.2) 1px, transparent 1px);
background-size: 50px 50px;
animation: moveParticles 10s linear infinite;
}
@keyframes moveParticles {
0% { background-position: 0 0; }
100% { background-position: 100px 100px; }
}
</style>
</head>
<body>
<!-- 粒子背景 -->
<div class="particles"></div>
<!-- 页面内容 -->
<h2>文件下载中心</h2>
<p>点击下面按钮下载压缩包</p>
<button onclick="window.location.href='download.zip'">下载 download.zip</button>
</body>
</html>

兼容IE版本
cpp
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<title>局域网下载页面(IE7兼容)</title>
<style type="text/css">
/* 页面整体样式 */
body {
margin:0;
padding:0;
font-family:"Microsoft YaHei", sans-serif;
text-align:center; /* 文字居中 */
background-color:#1f1c2c; /* 背景颜色,IE7兼容 */
color:#ffffff; /* 文字颜色 */
}
/* 标题样式 */
h2 {
font-size:24px;
margin-top:50px;
margin-bottom:10px;
}
/* 描述文字 */
p {
font-size:16px;
margin-bottom:30px;
}
/* 下载按钮 */
#downloadBtn {
display:inline-block;
padding:15px 50px;
font-size:16px;
color:#ffffff; /* 按钮文字颜色 */
background-color:#dd2476; /* 按钮背景颜色 */
border:none;
cursor:pointer;
}
/* 按钮悬浮效果 */
#downloadBtn:hover {
background-color:#ff512f; /* 鼠标悬浮改变颜色 */
}
</style>
</head>
<body>
<!-- 页面标题 -->
<h2>文件下载中心</h2>
<p>点击下面按钮下载压缩包</p>
<!-- 下载按钮 -->
<button id="downloadBtn">下载 download.zip</button>
<script type="text/javascript">
// 获取按钮对象
var btn = document.getElementById('downloadBtn');
// 点击按钮触发下载
btn.onclick = function() {
// 直接跳转到文件 URL 下载(兼容 IE7)
window.location.href = 'download.zip';
};
</script>
</body>
</html>
资源摆放如下所示:
