Qt 快速搭建局域网 HTTP 下载服务(兼容 IE/Chrome/Edge/Firefox)

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 或使用防火墙未开放端口,会导致其他设备无法访问。

三、解决方案

  1. 使用 QCoreApplication::applicationDirPath()
    取 EXE 所在目录,保证文件路径不受调试或工作目录影响:
    QString baseDir = QCoreApplication::applicationDirPath();
    QFile file(baseDir + "/download.zip");
    在代码中通过 ":/index.html" 和 ":/download.zip" 访问。
  2. 服务器返回 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 均支持。
  3. 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>

资源摆放如下所示:

相关推荐
q***69771 小时前
使用 Qt 插件和 SQLCipher 实现 SQLite 数据库加密与解密
数据库·qt·sqlite
极地星光2 小时前
Qt/C++ 单例模式深度解析:饿汉式与懒汉式实战指南
c++·qt·单例模式
John_ToDebug2 小时前
浏览器内核的“智变”:从渲染引擎到AI原生操作系统的征途
人工智能·chrome
板鸭〈小号〉4 小时前
应用层协议 HTTP
网络·网络协议·http
HIT_Weston4 小时前
45、【Ubuntu】【Gitlab】拉出内网 Web 服务:http.server 分析(二)
前端·http·gitlab
_OP_CHEN5 小时前
从零开始的Qt开发指南:(七)Qt常用控件之按钮类控件深度解析:从 QPushButton 到单选 / 复选的实战指南
qt·前端开发·qradiobutton·qpushbutton·qcheckbox·qt常用控件·gui界面开发
组合缺一5 小时前
Solon AI 开发学习6 - chat - 两种 http 流式输入输出
python·学习·http
友友马18 小时前
『QT』窗口 (一)
开发语言·数据库·qt
布朗克1681 天前
HTTP 与 HTTPS 的工作原理及其区别
http·https