C++云存储项目
来源:程序员老廖
1. 项目概述
1.1 功能特性
用户系统:支持用户注册、登录、登出
文件管理:文件上传、下载、删除、列表展示
媒体预览:视频在线播放、图片预览
文件分享:支持多种分享模式(公开/提取码/指定用户)
大文件优化:分块上传、断点续传
流式传输:视频首帧快速播放优化
1.2 项目特色
相比普通 Web 服务器项目,本项目的亮点:
大文件上传处理:基于状态机的分块解析,支持 GB 级文件
断点续传下载:完整支持 HTTP Range 请求
视频流式优化:智能缓冲控制,首帧秒开
企业级架构:基于 muduo 的 Reactor 高性能网络框架
2. 系统架构
2.1 总体架构图
┌──────────────────────────────────────────────────────────────────────────────────────┐ │ HTTP Client │ │ (Browser/移动端) │ └──────────────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────────────────────────┐ │ HTTP Server Layer │ │ ┌────────────────┐ ┌───────────────┐ ┌────────────────┐ ┌───────────────┐ │ │ │ 静态文件 │ │ 文件上传 │ │ 文件下载 │ │ 用户认证 │ │ │ │ 服务器 │ │ 处理器 │ │ 处理器 │ │ 系统 │ │ │ └────────────────┘ └───────────────┘ └────────────────┘ └───────────────┘ │ │ │ │ │ ┌────────────────▼───────────────┐ │ │ │ HttpUploadHandler │ │ │ │ (业务逻辑核心) │ │ │ └────────────────┬───────────────┘ │ └────────────────────────────────────────────┼─────────────────────────────────────────┘ │ ┌────────────────────────────────────────────┼─────────────────────────────────────────┐ │ MyMuduo Network Framework │ │ ┌──────────────────────────────────────────▼─────────────────────────────────────┐ │ │ │ HttpServer │ │ │ │ (HTTP Protocol Parser & Router) │ │ │ └──────────────────────────────────────────┬─────────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────────────────────▼─────────────────────────────────────┐ │ │ │ TcpServer │ │ │ │ (Connection Management & Event Distribution) │ │ │ └──────────────────────────────────────────┬─────────────────────────────────────┘ │ │ │ │ │ ┌──────────────────────────────────────────▼─────────────────────────────────────┐ │ │ │ EventLoop │ │ │ │ (epoll-based Reactor Pattern) │ │ │ └────────────────────────────────────────────────────────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────────────────┘ │ ▼ ┌──────────────────────────────────────────────────────────────────────────────────────┐ │ Data Layer │ │ ┌──────────────────────────────┐ ┌──────────────────────────────────────┐ │ │ │ MySQL 5.7+ │ │ File System │ │ │ │ - users │ │ - uploads/ │ │ │ │ - sessions │ │ - filename_mapping.json │ │ │ │ - files │ └──────────────────────────────────────┘ │ │ │ - file_shares │ │ │ └──────────────────────────────┘ │ └──────────────────────────────────────────────────────────────────────────────────────┘2.2 模块依赖关系
┌──────────────────────┐ │ application/ │ │ http_upload.cc │ └───────────┬──────────┘ │ 使用 ▼ ┌──────────────────────┐ │ net/http/ │ │ HttpServer │ └───────────┬──────────┘ │ 依赖 ▼ ┌─────────────────────────────┼──────────────────────────┐ ▼ ▼ ▼ ┌────────────────┐ ┌────────────────┐ ┌────────────────────┐ │ net/ │ │ base/ │ │ third_party │ │ TcpServer │ │ ThreadPool │ │ json.hpp │ │ Buffer │ │ Buffer │ │ │ │ EventLoop │ │ Logging │ │ │ └────────────────┘ └────────────────┘ └────────────────────┘2.3 网络模型架构(Reactor 模式)
当前项目采用 单 Reactor + 线程池 模型:
┌────────────────────────────────────────────────────────────────────────────────────┐ │ Main Thread (Reactor) │ │ ┌─────────────────────────────────────────────────────────────────────────────┐ │ │ │ EventLoop │ │ │ │ ┌───────────────┐ ┌────────────────┐ ┌─────────────────────┐ │ │ │ │ │ epoll │──▶ │ Channel │──▶ │ TcpConnection │ │ │ │ │ │ wait │ │ callback │ │ │ │ │ │ │ └───────────────┘ └────────────────┘ └───────────┬─────────┘ │ │ │ └────────────────────────────────────────────────────────────┼────────────────┘ │ └──────────────────────────────────────────────────────────────┼─────────────────────┘ │ IO Events (Read/Write/Accept) │ ▼ ┌─────────────────────────────────────────────────────────────────────────────────┐ │ Thread Pool (4 threads) │ │ ┌───────────────┐ ┌────────────────┐ ┌──────────────┐ ┌──────────────────┐ │ │ │ Worker 1 │ │ Worker 2 │ │ Worker 3 │ │ Worker 4 │ │ │ │ │ │ │ │ │ │ │ │ │ │ File Upload │ │ File Upload │ │ User Auth │ │ DB Query │ │ │ │ Processing │ │ Processing │ │ Processing │ │ Processing │ │ │ └───────────────┘ └────────────────┘ └──────────────┘ └──────────────────┘ │ │ │ │ Business Logic Processing │ └─────────────────────────────────────────────────────────────────────────────────┘项目代码领取:C++ Linux学完后做什么项目?推荐做C++ Linux云存储项目(附带完整项目源码和文档)
3. 核心技术栈
3.1 C++11 特性应用
本项目充分运用了 C++11 的现代特性:
智能指针管理资源
// TcpConnection 使用 shared_ptr 管理生命周期 using TcpConnectionPtr = std::shared_ptr<TcpConnection>; // 连接上下文存储 std::shared_ptr<HttpContext> context = std::make_shared<HttpContext>(); conn->setContext(context); // 弱引用避免循环引用 std::weak_ptr<TcpConnection> weakConn = conn;右值引用与移动语义
// Buffer 类的移动构造函数优化数据传输 class Buffer { Buffer(Buffer&& other) noexcept; Buffer& operator=(Buffer&& other) noexcept; }; // 在 HttpRequest 中使用移动减少拷贝 void appendToBody(const char* data, size_t len) { body_.append(data, len); // 内部使用移动语义 }Lambda 表达式注册回调
// 设置 HTTP 请求处理回调 server.setHttpCallback( [handler](const TcpConnectionPtr& conn, HttpRequest& req, HttpResponse* resp) { return handler->onRequest(conn, req, resp); }); // 设置写完成回调(用于分块下载) conn->setWriteCompleteCallback([this, downContext](const TcpConnectionPtr& connection) { std::string chunk; if (downContext->readNextChunk(chunk)) { connection->send(chunk); return true; // 继续写 } else { connection->shutdown(); // 完成,关闭连接 return true; } });原子操作与线程安全
// 活跃请求计数器(线程安全) std::atomic<int> activeRequests_{0}; // 在多个线程中安全递增 activeRequests_.fetch_add(1, std::memory_order_relaxed);3.2 线程与线程池
Thread 类封装
基于 pthread 封装,提供简洁的线程接口:
class Thread : noncopyable { public: explicit Thread(ThreadFunc func, const std::string& name); void start(); int join(); private: std::shared_ptr<pthread_t> pthreadId_; ThreadFunc func_; };ThreadPool 线程池
class ThreadPool : noncopyable { public: explicit ThreadPool(const std::string& nameArg); void start(int numThreads); void run(Task task); // 提交任务到队列 private: std::vector<std::unique_ptr<Thread>> threads_; BlockingQueue<Task> queue_; // 阻塞队列 };3.3 高性能组件
Buffer 高性能缓冲区
class Buffer { // 采用 vector<char> 作为底层存储 // 预留 prependable 空间用于高效头部插入 // 支持 scatter/gather IO std::vector<char> buffer_; size_t readerIndex_; size_t writerIndex_; public: void append(const char* data, size_t len); void retrieve(size_t len); std::string retrieveAllAsString(); };AsyncLogging 异步日志
class AsyncLogging : noncopyable { // 双缓冲技术:前端缓冲 + 后端缓冲 // 避免日志 IO 阻塞业务线程 void append(const char* logline, int len); private: BufferPtr currentBuffer_; // 当前写入缓冲 BufferPtr nextBuffer_; // 预备缓冲 BufferVector buffers_; // 待写入文件的缓冲队列 };4. 核心流程详解
4.1 文件上传流程
4.1.1 架构流程图
┌──────────────┐ POST /upload ┌─────────────────┐ │ Client │ ──────────────────▶ │ HttpServer │ │ (Browser │ Content-Type: │ │ │ /App) │ multipart/ │ │ └──────────────┘ form-data └─────────┬───────┘ │ ▼ ┌──────────────────┐ │ HttpUpload │ │ Handler │ └─────────┬────────┘ │ ┌───────────────────────┼───────────────────┐ │ │ │ ▼ ▼ ▼ ┌────────────┐ ┌──────────┐ ┌────────────┐ │ Parse │ │ Create │ │ Write to │ │ Boundary │ ────▶ │ Upload │ ─────▶ │ Disk │ │ Headers │ │ Context │ │ (chunked) │ └────────────┘ └──────────┘ └────────────┘ │ ▼ ┌──────────────┐ │ MySQL Insert │ │ Save metadata│ └──────────────┘4.1.2 详细时序图
4.1.3 状态机实现
文件上传采用状态机处理多部分表单数据:
class FileUploadContext { public: enum class State { kExpectHeaders, // 等待解析 multipart headers kExpectContent, // 等待文件内容 kExpectBoundary, // 等待下一个 boundary kComplete // 上传完成 }; void writeData(const char* data, size_t len); State getState() const { return state_; } void setState(State state) { state_ = state; } private: State state_; std::string boundary_; std::ofstream file_; uintmax_t totalBytes_; };状态转换逻辑:
┌──────────────────────────────────┐ │ kExpectHeaders │◀─────────────────┐ └─────────────────┬────────────────┘ │ │ 解析到 \r\n\r\n │ ▼ │ ┌──────────────────────────────────┐ 找到 boundary │ │ kExpectContent │──────────────────┘ └─────────────────┬────────────────┘ │ 找到结束 boundary (--) ▼ ┌──────────────────────────────────┐ │ kComplete │ └──────────────────────────────────┘4.1.4 关键代码解析
分块数据处理(http_upload.cc 第 540-620 行):
// 处理后续的数据块 std::string body = req.body(); if (!body.empty()) { switch (uploadContext->getState()) { case FileUploadContext::State::kExpectBoundary: { // 检查是否是结束边界 std::string endBoundary = uploadContext->getBoundary() + "--"; size_t endPos = body.find(endBoundary); if (endPos != std::string::npos) { if (endPos > 0) { uploadContext->writeData(body.data(), endPos); } uploadContext->setState(FileUploadContext::State::kComplete); break; } // 检查是否是普通边界 size_t boundaryPos = body.find(uploadContext->getBoundary()); if (boundaryPos != std::string::npos) { if (boundaryPos > 0) { uploadContext->writeData(body.data(), boundaryPos); } uploadContext->setState(FileUploadContext::State::kExpectContent); } else { // 没有找到边界,写入所有内容 uploadContext->writeData(body.data(), body.size()); } break; } // ... 其他状态处理 } }性能优化点:
直接写入磁盘:避免内存累积,支持 GB 级大文件
边读边写:无需等待完整请求体
零拷贝优化:使用 std::vector<char> 直接写入
4.2 文件下载与断点续传
4.2.1 流程图
┌──────────────┐ GET /download/{filename} ┌─────────────────┐ │ Client │ ───────────────────────────▶ │ HttpServer │ │ (Browser │ Headers: │ │ │ /App) │ X-Session-ID: xxx │ │ └──────────────┘ Range: bytes=0-1023 └────────┬────────┘ │ ▼ ┌───────────────┐ │ 权限检查 │ │ validate │ │ Session │ └───────┬───────┘ │ ┌─────────────────────┼───────────────────┐ │ │ │ 有权限 ▼ 无权限 ▼ 文件不存在 ▼ ┌────────────────┐ ┌──────────────┐ ┌────────────────┐ │ 创建下载 │ │ 403 │ │ 404 │ │ 上下文 │ │ Forbidden │ │ Not Found │ └───────┬────────┘ └──────────────┘ └────────────────┘ │ ▼ ┌─────────────────┐ │ 解析 Range │ │ 头部 │ └────────┬────────┘ │ ▼ ┌─────────────────┐ │ 设置响应头 │ │ 206/200 │ └────────┬────────┘ │ ▼ ┌─────────────┐ ┌─────────────────┐ │ 读取 1MB │ ──────▶ │ 发送数据 │ │ 数据块 │ │ │ └─────────────┘ └────────┬────────┘ │ 写完成 │ 回调 ▼ ┌─────────────────┐ │ 继续读/发送 │ │ 直到文件结束 │ └─────────────────┘4.2.2 时序图
4.2.3 断点续传实现
class FileDownContext { public: // 定位到指定位置 void seekTo(uintmax_t position) { file_.seekg(position); currentPosition_ = position; isComplete_ = false; } // 读取下一个数据块(1MB) bool readNextChunk(std::string& chunk) { const uintmax_t chunkSize = 1024 * 1024; // 1MB uintmax_t remainingBytes = fileSize_ - currentPosition_; uintmax_t bytesToRead = std::min(chunkSize, remainingBytes); if (bytesToRead == 0) { isComplete_ = true; return false; } std::vector<char> buffer(bytesToRead); file_.read(buffer.data(), bytesToRead); chunk.assign(buffer.data(), bytesToRead); currentPosition_ += bytesToRead; return true; } private: std::ifstream file_; uintmax_t fileSize_; uintmax_t currentPosition_; bool isComplete_; };4.2.4 Range 请求处理
// 解析 Range 头部 std::string rangeHeader = req.getHeader("Range"); uintmax_t startPos = 0; uintmax_t endPos = fileSize - 1; bool isRangeRequest = false; if (!rangeHeader.empty()) { std::regex rangeRegex("bytes=(\\d+)-(\\d*)"); std::smatch matches; if (std::regex_search(rangeHeader, matches, rangeRegex)) { startPos = std::stoull(matches[1]); if (!matches[2].str().empty()) { endPos = std::stoull(matches[2]); } isRangeRequest = true; } } // 设置响应状态码 if (isRangeRequest) { resp->setStatusCode(HttpResponse::k206PartialContent); resp->addHeader("Content-Range", "bytes " + std::to_string(startPos) + "-" + std::to_string(endPos) + "/" + std::to_string(fileSize)); } else { resp->setStatusCode(HttpResponse::k200Ok); } // 支持断点续传的头部 resp->addHeader("Accept-Ranges", "bytes"); resp->addHeader("Content-Length", std::to_string(endPos - startPos + 1));4.3 用户认证与 Session 管理
4.3.1 架构图
┌──────────────────────────────────────────────────────────────────────────────┐ │ Session Management │ ├──────────────────────────────────────────────────────────────────────────────┤ │ │ │ ┌────────────┐ ┌───────────────────┐ ┌──────────────┐ │ │ │ Login │──────────▶│ Session │─────────▶ │ MySQL │ │ │ │ │ │ Created │ │ sessions │ │ │ └────────────┘ └─────────┬─────────┘ │ table │ │ │ │ └──────────────┘ │ │ │ │ │ ┌────────────────┐ │ Session ID (32 chars) │ │ │ Client │◀─────────────────┘ stored in: │ │ │ │ - localStorage (Browser) │ │ └───────┬────────┘ - X-Session-ID header │ │ │ │ │ │ Subsequent Requests │ │ │ X-Session-ID: xxx │ │ ▼ │ │ ┌────────────┐ ┌────────────┐ ┌────────────┐ │ │ │ API │─────────▶ │ Validate │─────────▶ │ Update │ │ │ │ Request │ │ Session │ │ Expire │ │ │ └────────────┘ └──────┬─────┘ │ Time │ │ │ │ └────────────┘ │ │ Valid / Invalid │ │ │ │ │ ┌──────────────────┼──────────────────┐ │ │ ▼ ▼ ▼ │ │ Continue 401 Unauthorized 403 Forbidden │ │ Processing │ │ │ └──────────────────────────────────────────────────────────────────────────────┘4.3.2 登录时序图
4.3.3 Session 验证时序图
4.3.4 核心代码实现
Session ID 生成(http_upload.cc 第 1421-1435 行):
std::string generateSessionId() { std::random_device rd; std::mt19937 gen(rd()); std::uniform_int_distribution<> dis(0, 35); const char* chars = "abcdefghijklmnopqrstuvwxyz0123456789"; std::string sessionId; sessionId.reserve(32); for (int i = 0; i < 32; ++i) { sessionId += chars[dis(gen)]; } return sessionId; }Session 验证(http_upload.cc 第 1449-1479 行):
bool validateSession(const std::string& sessionId, int& userId, std::string& username) { if (sessionId.empty()) { return false; } // 查询未过期的会话 std::string query = "SELECT user_id, username FROM sessions WHERE session_id = '" + escapeString(sessionId) + "' AND expire_time > NOW()"; MYSQL_RES* result = executeQueryWithResult(query); if (!result || mysql_num_rows(result) == 0) { return false; } MYSQL_ROW row = mysql_fetch_row(result); userId = std::stoi(row[0]); username = row[1]; mysql_free_result(result); // 更新过期时间(滑动过期) std::string updateQuery = "UPDATE sessions SET expire_time = DATE_ADD(NOW(), INTERVAL 30 MINUTE) WHERE session_id = '" + escapeString(sessionId) + "'"; executeQuery(updateQuery); return true; }4.4 文件分享机制
4.4.1 分享类型说明
分享类型 | 说明 | 访问方式 |
|---|---|---|
private | 私有,不分享 | 仅文件所有者 |
public | 完全公开 | 任何人通过链接访问 |
protected | 需要提取码 | 链接 + 6位提取码 |
use | 指定用户 | 仅指定用户可访问 |
4.4.2 分享流程图
┌───────────────────────────────────────────────────────────────────────────────┐ │ File Share Flow │ ├───────────────────────────────────────────────────────────────────────────────┤ │ │ │ 1. 创建分享 │ │ ┌───────────────┐ ┌────────────────┐ ┌────────────────┐ │ │ │ 用户选择 │───────▶│ 生成 │───────▶ │ 写入 │ │ │ │ 分享类型 │ │ shareCode │ │ file_shares │ │ │ │ 设置过期 │ │ extract │ │ 表 │ │ │ │ 时间 │ │ Code(保护) │ │ │ │ │ └───────────────┘ └────────────────┘ └────────────────┘ │ │ │ │ │ ▼ │ │ ┌─────────────────┐ │ │ │ 返回分享链接 │ │ │ │ /share/{code} │ │ │ └─────────────────┘ │ │ │ │ 2. 访问分享 │ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │ │ 访问链接 │──────▶│ 查询 │──────▶│ 权限检查 │ │ │ │ /share/xx │ │ shareCode │ │ │ │ │ └──────────────┘ └──────────────┘ └───────┬──────┘ │ │ │ │ │ ┌───────────┼────────────┐ │ │ ▼ ▼ ▼ │ │ 公开文件 需要提取码 指定用户 │ │ │ │ │ │ │ ▼ ▼ ▼ │ │ 直接访问 验证extract 验证用户 │ │ Code 身份 │ │ │ └───────────────────────────────────────────────────────────────────────────────┘4.4.3 分享时序图
4.5 视频流式传输优化
4.5.1 问题背景
传统视频下载会导致:
用户需要等待整个文件下载完成才能播放
大视频文件消耗大量带宽
用户可能只看几秒钟就关闭
4.5.2 优化方案
┌────────────────────────────────────────────────────────────────────┐ │ Video Streaming Optimization │ ├────────────────────────────────────────────────────────────────────┤ │ │ │ 传统方式 优化后 │ │ ┌───────────────┐ ┌──────────────────┐ │ │ │ 下载整个 │ 10秒+ 才能播放 │ 首帧缓冲 │ 1秒内 │ │ │ 文件 │ │ 2-5秒 │ 播放 │ │ │ (100MB) │ │ │ │ │ └───────────────┘ └────────┬─────────┘ │ │ │ │ │ ▼ │ │ ┌──────────────┐ │ │ │ 暂停下载 │ │ │ │ 保持连接 │ │ │ │ 可用状态 │ │ │ └───────┬──────┘ │ │ │ │ │ 用户点击播放 ────────▶│ │ │ ▼ │ │ ┌──────────────┐ │ │ │ 恢复下载 │ │ │ │ 流式传输 │ │ │ └──────────────┘ │ │ │ │ 关键技术: │ │ 1. HTTP 206 Partial Content (Range 请求) │ │ 2. 视频 moov atom 前置 (ffmpeg -movflags faststart) │ │ 3. 智能缓冲控制 (metadata 加载后暂停) │ │ │ └────────────────────────────────────────────────────────────────────┘4.5.3 前端缓冲控制逻辑(index.html)
// 关键代码:index.html 第 1206-1270 行 videoElement.addEventListener('loadedmetadata', function() { // 保存原始视频URL和时长信息 videoElement.dataset.originalSrc = videoElement.src; videoElement.dataset.duration = videoElement.duration; // 目标缓冲时间:5秒 或 视频总时长的 10% const targetBufferTime = 5; const minBufferPercent = 0.1; const minBufferSeconds = Math.min(targetBufferTime, videoElement.duration * minBufferPercent); // 设置缓冲检查定时器 const bufferCheckInterval = setInterval(() => { if (videoElement.hasAttribute('data-played')) { clearInterval(bufferCheckInterval); return; } // 检查缓冲进度 if (videoElement.buffered.length > 0) { const bufferedEnd = videoElement.buffered.end(0); // 缓冲足够数据后中断连接 if (bufferedEnd >= minBufferSeconds) { clearInterval(bufferCheckInterval); videoElement.pause(); videoElement.removeAttribute('src'); videoElement.load(); // 终止网络请求 } } }, 500); // 最大等待10秒,防止无限等待 setTimeout(() => { if (!videoElement.hasAttribute('data-played')) { clearInterval(bufferCheckInterval); // 至少有1秒数据就中断 if (videoElement.buffered.length > 0 && videoElement.buffered.end(0) >= 1) { videoElement.pause(); videoElement.removeAttribute('src'); videoElement.load(); } } }, 10000); }); // 用户点击播放时恢复下载 videoElement.addEventListener('play', function() { videoElement.setAttribute('data-played', 'true'); if (!videoElement.hasAttribute('data-playing')) { videoElement.setAttribute('data-playing', 'true'); if (!videoElement.src && videoElement.dataset.originalSrc) { videoElement.src = videoElement.dataset.originalSrc; } } });4.5.4 优化效果对比
指标 | 优化前 | 优化后 |
|---|---|---|
首帧时间 | 10秒+ | 1-3秒 |
初始流量消耗 | 100% 文件大小 | 2-5% 文件大小 |
服务器连接占用 | 持续直到完整下载 | 按需恢复 |
用户体验 | 差(长时间等待) | 好(秒开播放) |
5.项目运行
5.1 环境要求
操作系统:Linux (Ubuntu 18.04+ / CentOS 7+)
编译器:g++ 7.0+ (支持 C++17)
CMake:3.10+
MySQL:5.7+ 或 8.0
依赖库:
libmysqlclient-dev (MySQL C API)
nlohmann/json (单头文件 JSON 库,已包含)
5.2 数据库初始化
# 进入项目目录 # 导入数据库(替换为你的 MySQL 用户名和密码) mysql -u root -p123456 < file_manager.sql # 验证导入成功 mysql -u root -p123456 -e "USE file_manager; SHOW TABLES;"5.3 编译运行
# 创建构建目录 mkdir build && cd build # 生成构建文件 cmake .. # 编译 make -j4 # 运行服务器 ./bin/http_upload # 访问 http://localhost:80005.4 配置文件说明
数据库连接配置在 http_upload.cc 中:
// HttpUploadHandler 构造函数(第 277-291 行) HttpUploadHandler(int numThreads, const std::string& dbHost = "localhost", const std::string& dbUser = "root", const std::string& dbPassword = "123456", const std::string& dbName = "file_manager", unsigned int dbPort = 3306)修改默认配置:
// main 函数中创建 handler 时指定 auto handler = std::make_shared<HttpUploadHandler>(4, // 线程数 "localhost", // 主机 "root", // 用户名 "your_password", // 密码 "file_manager", // 数据库名 3306); // 端口项目代码领取:C++ Linux学完后做什么项目?推荐做C++ Linux云存储项目(附带完整项目源码和文档)
