Linux(CentOS)/Windows-C++ 云备份项目(服务器网络通信模块,业务处理模块设计,断点续传设计)

news2024/12/28 18:38:11

此模块将网络通信模块和业务处理模块进行了合并

  1. 网络通信通过httplib库搭建完成
  2. 业务处理:
    • 文件上传请求:备份客户端上传的文件,响应上传成功
    • 客户端列表请求:客户端请求备份文件的请求页面,服务器响应
    • 文件下载请求:通过展示的文件列表,点击下载,服务器响应下载的文件数据

文章目录

  • 1. 网络通信模块设计
  • 2. 业务处理模块设计
    • 文件上传业务处理 /upload请求
    • 展示备份文件页面 / /listshow请求
    • 文件下载业务处理 /download
      • 断点续传原理:
  • 3. 服务器代码:
  • 4. 代码位置

1. 网络通信模块设计

文件下载Http请求部分如下:通过分隔符可以取到文件数据和文件的其他信息,文件信息和文件内容之间空行隔开。这个解析过程httplib库已经封装完毕
在这里插入图片描述

网络通信请求设计:

  1. 文件上传:服务器收到/upload 是为文件上传
  2. 展示页面:服务器收到/listshow是服务器所有备份文件展示
    响应 HTTP/1.1 200 OK + 构造html正文界面
  3. 文件下载:服务器收到/download/文件名 为文件下载请求
    响应 HTTP/1.1 200 OK +文件数据(正文)

2. 业务处理模块设计

文件上传业务处理 /upload请求

#pragma once
#include "backups.hpp"
#include "./httplib/httplib.h"
#include "./config/config.hpp"
extern CloudBackups::DataMange *dataMange;
namespace CloudBackups
{
    class Server
    {
    private:
        int port;
        std::string ip;
        std::string download_prefix;
        httplib::Server server;
        // 上传文件
        static void Upload(const httplib::Request &request, httplib::Response &response)
        {
            LOG(INFO, "upload begin");
            // POST请求,文件数据在http正文中,分区存储
            bool ret = request.has_file("file"); // 判断有无上传文件字段
            if (ret == false)
            {
                LOG(ERROR, "request error!");
                response.status = 400;
                return;
            }
            // 获取数据
            const auto &file = request.get_file_value("file");
            std::string backdir = Config::GetInstance()->GetBackDir();
            // 保存文件
            std::string filepath = backdir + FileUtil(file.filename).filename(); // 实际路径+文件名
            FileUtil stream(filepath);
            stream.setContent(file.content);
            // 更新文件信息Json文件
            BackupInfo info(filepath);
            dataMange->Insert(info);
            LOG(INFO, "upload success");
        }
        // 展示页面
        static void ListShow(const httplib::Request &request, httplib::Response &response)
        {
        }
        // 下载文件
        static void Download(const httplib::Request &request, httplib::Response &response)
        {
        }

    public:
        Server()
        {
            Config *config = Config::GetInstance();
            port = config->GetServerPort();
            ip = config->GetServerIp();
            download_prefix = config->GetDownloadPrefix();
            LOG(INFO, "init server success");
        }
        bool RunMoudle()
        {
            LOG(INFO, "server running");
            // 搭建Http服务器
            server.Post("/upload", Upload); // 文件上传
            server.Get("/list", ListShow);  // 展示页面
            server.Get("/", ListShow);      // 网页根目录也是展示页面
            std::string download_url = download_prefix + "(.*)";
            server.Get(download_url, Download); // 下载文件,正则表达式捕捉要下载的文件
            if (server.listen(ip, port) == false)
            {
                LOG(FATAL, "server listen failed! ip=" + ip);
                return false;
            }
            return true;
        }
    };
}

单元测试运行截图

// #include "util/fileutil.hpp"
#include <vector>
#include "util/json.hpp"
#include "config/config.hpp"
#include "backups.hpp"
#include "hot.hpp"
#include "server.hpp"
CloudBackups::DataMange *dataMange;
void ServerUtilTest()
{
    CloudBackups::Server server;
    dataMange = new CloudBackups::DataMange();
    server.RunMoudle();
}
int main(int argc, char const *argv[])
{
    ServerUtilTest();
    return 0;
}

在这里插入图片描述
上传文件的信息Json如下:
在这里插入图片描述

展示备份文件页面 / /listshow请求

#pragma once
#include "backups.hpp"
#include "./httplib/httplib.h"
#include "./config/config.hpp"
extern CloudBackups::DataMange *dataMange;
namespace CloudBackups
{
    class Server
    {
    private:
        int port;
        std::string ip;
        std::string download_prefix;
        httplib::Server server;
        // 上传文件
        static void Upload(const httplib::Request &request, httplib::Response &response)
        {
        }
        // 展示页面
        static void ListShow(const httplib::Request &request, httplib::Response &response)
        {
            LOG(INFO, "list show begin");
            // 获取所有文件信息
            std::vector<BackupInfo> array;
            dataMange->GetAll(array);
            // 根据所有文件信息构建http响应
            std::stringstream ss;
            ss << R"(<!DOCTYPE html><html lang="cn"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width, initial-scale=1.0"><title>download list</title></head><body>)";
            ss << R"(<h1 align="center">Download List</h1>)";
            for (auto &info : array)
            {
                std::string filename = FileUtil(info.real_path).filename();
                ss << R"(<tr><td><a href=")" << info.url << R"(">)" << filename << "</a></td>";
                ss << R"(<td align="right"> )" << convertTimeStamp2TimeStr(info.mtime) << "  </td>";
                ss << R"(<td align="right">)" << info.size / 1024 << "Kb</td></tr>";
                ss << "<br>";
            }
            ss << "</body></html>";
            response.body = ss.str();
            response.set_header("Content-Type", "text/html");
            response.status = 200;
            LOG(INFO, "list show end");
        }
        // 下载文件
        static void Download(const httplib::Request &request, httplib::Response &response)
        {
        }

    public:
        Server()
        {
            Config *config = Config::GetInstance();
            port = config->GetServerPort();
            ip = config->GetServerIp();
            download_prefix = config->GetDownloadPrefix();
            LOG(INFO, "init server success");
        }
        bool RunMoudle()
        {
            LOG(INFO, "server running");
            // 搭建Http服务器
            server.Post("/upload", Upload); // 文件上传
            server.Get("/list", ListShow);  // 展示页面
            server.Get("/", ListShow);      // 网页根目录也是展示页面
            std::string download_url = download_prefix + "(.*)";
            server.Get(download_url, Download); // 下载文件,正则表达式捕捉要下载的文件
            if (server.listen(ip, port) == false)
            {
                LOG(FATAL, "server listen failed! ip=" + ip);
                return false;
            }
            return true;
        }
    };
}

在这里插入图片描述
在这里插入图片描述

文件下载业务处理 /download

http的ETag头部字段:存储了一个资源的唯一标识

客户端第一次请求文件时会收到响应信息。

客户端第二次下载时,客户端会把这个信息发送给服务器,让这个服务器根据这个标识判断这个资源有没有被修改锅。如果没修改过。客户端直接使用缓存区的资源。如果改过则重新修改

http对ETag字段没有定义,这里设定:
ETags:文件名称-文件大小-最后修改时间 构成

ETags字段也用于断点续传,断点续传也需要保证文件没有被修改

http协议的Accept-Ranges:bytes字段用于表示支持断点续传。数据以字节结尾

Content-Type:字段决定了浏览器如何处理响应正文,用来区分下载还是html显示。
Content-Type:application/octet-stream常用于文件下载

断点续传原理:

文件下载时由于异常而中断,如果从头下载效率较低,需要将之前传输过的数据效率太低。断点续传的目的为了提高上传效率

实现:客户端在下载时需要记录当前下载的位置。当下载中断时,下次断点续传时将下载起始位置发送给服务器。服务器收到后仅仅回传客户端需要的数据即可

如果下载文件后这个文件在服务器上被修改了,这时候需要将文件重新下载

http中断点续传关键点在于告诉服务器下载区间范围,服务器上要检测这个文件是否被修改。
http协议的Accept-Ranges:bytes字段用于表示支持断点续传
ETag文件唯一标识符,客户端收到响应会保存这个信息

请求:

GET /download/test.txt HTTP/1.1
If-Range:“服务端在下载时响应ETag字段搭配使用判断文件是否被修改,常用于恢复下载”
Range: bytes=100-200(区间范围) 这个字段用来告诉客户端需要的数据范围

响应:

HTTP/1.1 206(服务器处理部分get请求) Paritial Content
ETag:”xxxx“(响应资源的版本标识符,判断文件是否被修改)
Content-Range: bytes 100-200(范围)
Accept-Ranges: bytes 字段用于表示支持断点续传

正文就是对应区间的数据

真正实现时:cpp-httplib会自动根据请求Range字段对response.body进行切片返回,封装实现。直接把response.body全部设置为文件所有内容即可

#pragma once
#include "backups.hpp"
#include "../httplib/httplib.h"
#include "../config/config.hpp"
extern CloudBackups::DataMange *dataMange;
namespace CloudBackups
{
    class Server
    {
    private:
        int port;
        std::string ip;
        std::string download_prefix;
        httplib::Server server;
        // ETag为设计者自行指定 ETags:文件名称-文件大小-最后修改时间 构成
        static std::string GetETag(BackupInfo info)
        {
            std::string etag = FileUtil(info.real_path).filename();
            etag += "-";
            etag += std::to_string(info.size);
            etag += "-";
            etag += std::to_string(info.mtime);
            return etag;
        }
        // 下载文件
        static void Download(const httplib::Request &request, httplib::Response &response)
        {
            // 1. 获取客户端请求资源的路径 request.path
            // 2. 根据路径获取文件备份信息
            BackupInfo info;
            if (dataMange->GetByUrl(request.path, info) == false)
            {
                LOG(WARNING, "file /download not found");
                response.status = 404;
                return;
            }
            // 3. 判断文件是否被压缩,被压缩的话需要先解压缩,删除压缩包,修改备份信息
            if (info.packflag == true)
            {
                // 被压缩,解压到backdir目录浏览
                FileUtil tool(info.pack_path);
                tool.unzip(info.real_path);
                // 删除压缩包
                tool.removeFile();
                info.packflag = false;
                // 修改配置文件
                dataMange->UpDate(info);
            }
            //  4. 读取文件数据放入body中
            FileUtil tool(info.real_path);
            tool.getContent(response.body);
            // 判断断点续传
            bool retrans = false; // 标记断点续传
            std::string befetag;
            if (request.has_header("If-Range"))
            {
                // 断点续传 服务端在下载时响应ETag字段搭配使用判断文件是否被修改
                befetag = request.get_header_value("If-Range");
                if (befetag == GetETag(info))
                {
                    // 文件没修改过
                    retrans = true;
                }
            }
            // 没有If-Range字段或者If-Range字段与ETag不匹配,重新下载
            if (retrans == false)
            {
                // 正常下载
                //  5. 设置响应头部字段ETag Accept-Range字段
                response.set_header("ETag", GetETag(info));
                response.set_header("Accept-Ranges", "bytes");
                response.set_header("Content-Type", "application/octet-stream");
                response.status = 200;
            }
            else
            {
                // 断点续传,了解区间范围
                response.set_header("ETag", GetETag(info));
                response.set_header("Accept-Ranges", "bytes");
                response.status = 206; // cpp-httplib会自动根据请求Range字段对response.body进行切片返回,封装实现
            }
            LOG(INFO, "download success");
        }

    public:
        Server()
        {
            Config *config = Config::GetInstance();
            port = config->GetServerPort();
            ip = config->GetServerIp();
            download_prefix = config->GetDownloadPrefix();
            // 创建文件夹
            FileUtil tool;
            tool.mkdir(Config::GetInstance()->GetBackDir());
            tool.mkdir(Config::GetInstance()->GetPackfileDir());
            LOG(INFO, "init server success");
        }
        bool RunMoudle()
        {
            LOG(INFO, "server running");
            // 搭建Http服务器
            server.Post("/upload", Upload); // 文件上传
            server.Get("/list", ListShow);  // 展示页面
            server.Get("/", ListShow);      // 网页根目录也是展示页面
            std::string download_url = download_prefix + "(.*)";
            // LOG(INFO, "DEBUG:" + download_url);
            server.Get(download_url, Download); // 下载文件,正则表达式捕捉要下载的文件
            if (server.listen(ip, port) == false)
            {
                LOG(FATAL, "server listen failed! ip=" + ip);
                return false;
            }
            return true;
        }
    };
}

3. 服务器代码:

#include <vector>
#include "../util/json.hpp"
#include "../config/config.hpp"
#include "backups.hpp"
#include "hot.hpp"
#include "server.hpp"
#include <thread>
CloudBackups::DataMange *dataMange;
void ServerRun()
{
    CloudBackups::Server server;
    dataMange = new CloudBackups::DataMange();
    server.RunMoudle();
}
void HotRun()
{
    dataMange = new CloudBackups::DataMange();
    CloudBackups::HotMange hot;
    hot.RunModule();
}
int main(int argc, char const *argv[])
{
    // 启动热点管理模块
    std::thread hot_thread(HotRun);
    std::thread server_thread(ServerRun);
    hot_thread.join();
    server_thread.join();
    return 0;
}

4. 代码位置

至此,项目服务器所有业务处理完毕
Gitee
Github

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1549578.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Div4 898 G,H

Here G. ABBC or BACB 解题思路 一个可以向左或向右吃掉一段连续的将连续的合成一个则字符串变为每个之间被隔开统计变化后和的数量为若&#xff0c;则若&#xff0c;则只会大1&#xff0c;即有一段没被吃掉&#xff0c;则让长度最小的剩下&#xff0c;省略号间的的个数不影响…

Partisia Blockchain:真正做到兼顾隐私、高性能和可拓展的公链

目前&#xff0c;包括 Secret Network、Oasis Protocol 等在内的绝大多数以隐私为特性的可编程公链&#xff0c;在兼顾隐私的同时&#xff0c;在可拓展以及性能上或多或少的有所牺牲&#xff0c;即难以对诸多实际应用场景进行支撑。这归咎于链的设计以及共识机制的不合理&#…

C++ explicit隐式类型转换

单参数构造函数支持隐式类型的转换 什么意思&#xff1f; 简单来理解就是&#xff1a; 一个类对象的构造函数的参数只有一个&#xff0c;就可以直接进行赋值传参 例如构造函数的参数为int&#xff0c;且只有一个int 就可以直接将int类型的整型数据转换成类对象 也就是说从int类…

JavaScript、ES6与微信小程序:工具箱、升级与新房子

JavaScript、ES6和微信小程序三者之间有什么联系&#xff1f;我想&#xff0c;作为初学者还是有点蒙。下面作一个简单的分析&#xff0c;供大家参考。 首先,我们可以把JavaScript想象成一个非常强大的工具箱,里面装满了各种各样的工具。这些工具可以帮助我们完成各种任务,比如…

SpringBoot集成 itextpdf 根据模板动态生成PDF

目录 需求说明前期准备Spring Boot 集成添加依赖构建工具类构建MultipartFile编辑PDF模板Java代码设置对应form的key-value 需求说明 根据合同模板&#xff0c;将动态的合同标签&#xff0c;合同方以及合同签约时间等动态的生成PDF&#xff0c;供用户下载打印。 前期准备 安…

C语言看完我这篇编译与链接就够啦!!!

1. 前言 Hello&#xff01;大家好我是小陈&#xff0c;今天来给大家介绍最详细的C语言编译与链接。 2. 编译和链接 我们通常用的编译器&#xff0c;比如Visual Sudio,这样的IDE(集成开发环境&#xff09;一般将编译和链接的过程一步完成&#xff0c;通常将这这种编译和链接合…

腾讯云4核8G服务器性能测评_CPU内存性能_带宽流量_系统盘

腾讯云4核8G服务器价格&#xff1a;轻量4核8G12M优惠价格646元15个月、CVM S5服务器4核8G配置1437元买1年送3个月。腾讯云4核8G服务器支持多少人同时在线&#xff1f;支持30个并发数&#xff0c;可容纳日均1万IP人数访问。腾讯云百科txybk.com整理4核8G服务器支持多少人同时在线…

MySQL数据库 - 单表查询(三)

一个不知名大学生&#xff0c;江湖人称菜狗 original author: Jacky Li Email : 3435673055qq.com Time of completion&#xff1a;2024.03.24 Last edited: 2024.03.24 目录 第1关&#xff1a;对查询结果进行排序 任务描述 相关知识 对查询结果排序 指定排序方向 编程要…

设计模式之建造者模式精讲

也叫生成器模式。将一个复杂的构建与它的表示分离&#xff0c;使得同样的构建过程可以创建不同的表示。 在建造者模式中&#xff0c;有如下4个角色&#xff1a; 抽象建造者&#xff08;Builder&#xff09;&#xff1a;用于规范产品的各个组成部分&#xff0c;并进行抽象&…

U盘未格式化,数据恢复攻略大揭秘

U盘遭遇未格式化困境&#xff0c;数据安全岌岌可危 在日常的工作和生活中&#xff0c;U盘以其便携、容量大的特性成为了我们不可或缺的存储工具。然而&#xff0c;有时我们会遇到这样一个棘手的问题&#xff1a;当我们将U盘插入电脑时&#xff0c;却收到了“未格式化”的提示。…

Vue3新手教程

Vue3新手教程 一. Vue3简介1. 性能的提升2.源码的升级3. 拥抱TypeScript4. 新的特性 二. 创建Vue3工程1. 基于 vue-cli 创建2. 基于 vite 创建(推荐)3. 一个简单的效果 三. Vue3核心语法1. OptionsAPI 与 CompositionAPI2. 拉开序幕的 setup2.1 setup 概述2.2 setup 的返回值2.…

【LVGL-键盘部件,实体按键控制】

LVGL-二维码库 ■ LVGL-键盘部件■ 示例一&#xff1a;键盘弹窗提示■ 示例二&#xff1a;设置键盘模式■ 综合示例&#xff1a; ■ LVGL-实体按键控制■ 简介 ■ LVGL-键盘部件 ■ 示例一&#xff1a;键盘弹窗提示 lv_keyboard_set_popovers(kb,true);■ 示例二&#xff1a;设…

拿到今日现货白银价格 如何开始分析?

很多投资者看到近期现货白银的强劲涨势&#xff0c;并且开户入场。但入场之后&#xff0c;他们发现对如何找到机会还不甚了解。比方说我们拿到今日现货白银价格要如何开始分析呢&#xff0c;很多新入场的投资者根本没有头绪&#xff0c;下面我们就来讨论一下相关的方法。 我们可…

PyTorch 教程-快速上手指南

文章目录 PyTorch Quickstart1.处理数据2.创建模型3.优化模型参数4.保存模型5.加载模型 PyTorch 基础入门1.Tensors1.1初始化张量1.2张量的属性1.3张量运算1.3.1张量的索引和切片1.3.2张量的连接1.3.3算术运算1.3.4单元素张量转变为Python数值 1.4Tensor与NumPy的桥接1.4.1Tens…

HCIP-Datacom(H12-821)题库补充(3/26)

最新 HCIP-Datacom&#xff08;H12-821&#xff09;完整题库请扫描上方二维码访问&#xff0c;持续更新中。 在运行STP的网络中&#xff0c;网络拓扑改变时会发送多种拓扑改变信息&#xff0c;在RSTP的网络中定义了几种拓扑改变信息? A&#xff1a;一种 B&#xff1a;二种 …

【前端学习——js篇】6.事件模型

具体见&#xff1a;https://github.com/febobo/web-interview 6.事件模型 ①事件与事件流 事件(Events) 事件是指页面中发生的交互行为&#xff0c;比如用户点击按钮、键盘输入、鼠标移动等。在js中&#xff0c;可以通过事件来触发相应的操作&#xff0c;例如执行函数、改变…

Rust编程(三)生命周期与异常处理

生命周期 生命周期&#xff0c;简而言之就是引用的有效作用域。在大多数时候&#xff0c;我们无需手动的声明生命周期&#xff0c;因为编译器可以自动进行推导。生命周期的主要作用是避免悬垂引用&#xff0c;它会导致程序引用了本不该引用的数据&#xff1a; {let r;{let x …

Autosar-CanNm、Nm配置详解(免费)-1

3.1由DBC创建Nm、CanNM ETAS工具可根据DBC文件&#xff0c;自动配置生成Nm、CanNm模块。但是关键的一点是要生成NM、CanNM模块DBC文件中必须有NM类型的报文。 还有一点&#xff0c;即使DBC文件中有Nm的报文&#xff0c;但是因为报文的类型在导入时没有设置成Nm&#xff0c;那也…

Springboot整合Redis报错:Unable to connection Redis

今天在做Springboot整合Redis中碰到下列错误&#xff1a; 基于以上的错误首先在Xshell或者其他远程操控虚拟机的软件上看能不能连接到Redis: [zzllocalhost ~]$ redis-cli -h 192.168.136.132 -p 6379 -a ****** Warning: Using a password with -a or -u option on the comma…

kubectl 启用shell自动补全功能

官网手册参考&#xff1a;https://kubernetes.io/zh-cn/docs/tasks/tools/install-kubectl-linux/ 系统&#xff1a;centos7 补全脚本依赖于工具 bash-completion&#xff0c; 所以要先安装它&#xff08;可以用命令 type _init_completion 检查 bash-completion 是否已安装&a…