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

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

  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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

vector类(一)

文章目录 vector介绍和使用1.vector的介绍2.vector的使用2.1 vector的定义2.2 vector iterator的使用2.3 vector空间增长问题2.4 vector增删查改2.5 vector迭代器失效问题 3.vector 在OJ中的使用 vector介绍和使用 1.vector的介绍 vector是表示 可变大小数组的 序列容器。 就…

【jmeter+ant+jenkins】之搭建 接口自动化测试平台

平台搭建 (1). 录制jmeter脚本 (2). 将jmeter的安装目录下的G:\jmeter\apache-jmeter-5.1.1\extras中&#xff0c;将 ”ant-jmeter-1.1.1.jar”文件放到 ant的lib目录下 (3). 配置jmeter的xml配置文件&#xff0c;并放在ant目录的bin目录下&#xff0c;使用ant编译验证jmeter的…

【书生·浦语大模型实战营第二期】学习笔记1

1. Introduction 开源llm举例&#xff1a;LLaMA 、Qwen 、Mistral 和Deepseek 大型语言模型的发展包括预训练、监督微调&#xff08;SFT&#xff09;和基于人类反馈的强化学习&#xff08;RLHF&#xff09;等主要阶段 InternLM2的显著特点 采用分组查询注意力&#xff08;GQA…

2014年认证杯SPSSPRO杯数学建模C题(第一阶段)土地储备方案的风险评估全过程文档及程序

2014年认证杯SPSSPRO杯数学建模 C题 土地储备方案的风险评估 原题再现&#xff1a; 土地储备&#xff0c;是指市、县人民政府国土资源管理部门为实现调控土地市场、促进土地资源合理利用目标&#xff0c;依法取得土地&#xff0c;进行前期开发、储存以备供应土地的行为。土地…

保姆级指导0基础如何快速搭建“对话机器人”类ChatGPT

参考了CDSN上的文章&#xff0c;但发现不work&#xff0c; 不是这里有问题&#xff0c;就是那里有问题&#xff0c;查阅了大量的资料&#xff0c;做了无数次试验&#xff0c;终于整理出来了一个完整的教程&#xff0c;保可用&#xff0c;保真~~~~~如果各位遇到什么问题&#xf…

【Leetcode每日一题】 递归 - 计算布尔二叉树的值(难度⭐⭐)(44)

1. 题目解析 题目链接&#xff1a;2331. 计算布尔二叉树的值 这个问题的理解其实相当简单&#xff0c;只需看一下示例&#xff0c;基本就能明白其含义了。 2.算法原理 算法思路概述&#xff1a; 问题解释&#xff1a;我们面对的是一个节点可能含有逻辑运算符&#xff08;AN…

探索智慧农业精准除草,基于高精度YOLOv8全系列参数【n/s/m/l/x】模型开发构建农田作物场景下杂草作物分割检测识别分析系统

智慧农业是未来的一个新兴赛道&#xff0c;随着科技的普及与落地应用&#xff0c;会有更加广阔的发展空间&#xff0c;关于农田作物场景下的项目开发实践&#xff0c;在我们前面的博文中也有很堵相关的实践&#xff0c;单大都是偏向于目标检测方向的&#xff0c;感兴趣可以自行…

opencv如何利用掩码将两张图合成一张图

最近在学opencv, 初学者。 里面有提到如何将两张图合成一张图, 提供了两个方法 一种是直接通过图片透明度权重进行融合 img1 = cv.imread(ml.png) img2 = cv.imread(opencv-logo.png) dst = cv.addWeighted(img1,0.7,img2,0.3,0) cv.imshow(dst,dst) cv.waitKey(0) cv.des…

6.二叉树——2.重建树

已知先序和中序序列 根据先序序列找到树根根据树根和中序序列找到左右子树 同理根据后序序列和中序序列也能重构树&#xff0c;但前序和后序不可以 递归coding思路 设先序序列为preorder[n]&#xff0c;中序序列为midorder[n] 大事化小&#xff1a; 确定根&#xff0c;即树…

Chrome 插件 storage API 解析

Chrome.storage API 解析 使用 chrome.storage API 存储、检索和跟踪用户数据的更改 一、各模块中的 chrome.storage 内容 1. Service worker 中 runtime 内容 2. Action 中 runtime 内容 3. Content 中 runtime 内容 二、权限&#xff08;Permissions&#xff09; 如果需使…

Ubuntu 配置 kubernetes 学习环境,让外部访问 dashboard

Ubuntu 配置 kubernetes 学习环境 一、安装 1. minikube 首先下载一下 minikube&#xff0c;这是一个单机版的 k8s&#xff0c;只需要有容器环境就可以轻松启动和学习 k8s。 首先你需要有Docker、QEMU、Hyperkit等其中之一的容器环境&#xff0c;以下使用 docker 进行。 对…

CleanMyMac X2024专业免费的国产Mac笔记本清理软件

非常高兴有机会向大家介绍CleanMyMac X 2024这款专业的Mac清理软件。它以其强大的清理能力、系统优化效果、出色的用户体验以及高度的安全性&#xff0c;在Mac清理软件市场中独树一帜。 CleanMyMac X2024全新版下载如下: https://wm.makeding.com/iclk/?zoneid49983 一、主要…

Nuxt2 渲染时html比css加载快,导致闪屏/CSS样式迟滞/抖动问题记录

问题场景&#xff1a; 最近在用Nuxt2重写公司官网&#xff0c;但因为笔者不是专业前端&#xff0c;之前虽然也用vue2来写前端&#xff0c;但是用nuxt2来写项目还是第一次。在开发过程中虽然也磕磕碰碰&#xff0c;但因为开发的是官网&#xff0c;偏CMS型的网站&#xff0c;所以…

Wireshark使用相关

1.wireshark如何查看RST包 tcp.flags.reset1 RST表示复位&#xff0c;用来异常的关闭连接&#xff0c;在TCP的设计中它是不可或缺的。发送RST包关闭连接时&#xff0c;不必等缓冲区的包都发出去&#xff08;不像上面的FIN包&#xff09;&#xff0c;直接就丢弃缓存区的包发送R…

安科瑞路灯安全用电云平台解决方案【电不起火、电不伤人】

背景介绍 近年来 &#xff0c;随着城市规模的不断扩大 &#xff0c;路灯事业蓬勃发展。但有的地方因为观念、技术、管理等方面不完善 &#xff0c;由此引发了一系列安全问题。路灯点多面广 &#xff0c;一旦漏电就极容易造成严重的人身安全事故。不仅给受害者家庭带来痛苦 &am…

亚信安全荣获2023年度5G创新应用评优活动两项大奖

近日&#xff0c;“关于2023 年度5G 创新应用评优活动评选结果”正式公布&#xff0c;亚信安全凭借在5G安全领域的深厚积累和创新实践&#xff0c;成功荣获“5G技术创新的优秀代表”和“5G应用创新的杰出实践”两项大奖。 面向异构安全能力的5G安全自动化响应系统 作为5G技术创…

架构师之路--Docker的技术学习路径

Docker 的技术学习路径 一、引言 Docker 是一个开源的应用容器引擎&#xff0c;它可以让开发者将应用程序及其依赖包打包成一个可移植的容器&#xff0c;然后在任何支持 Docker 的操作系统上运行。Docker 具有轻量级、快速部署、可移植性强等优点&#xff0c;因此在现代软件开…

软件接口安全设计规范及审计要点

1.token授权安全设计 2.https传输加密 3.接口调用安全设计 4.日志审计里监控 5.开发测试环境隔离&#xff0c;脱敏处理 6.数据库运维监控审计 项目管理全套资料获取&#xff1a;软件开发全套资料_数字中台建设指南-CSDN博客

自营商城私域商城的选品上货如何借助API实现自动化商品采集商品搜索无货源?

商业智能时代的来临&#xff0c;在线化、网络化、智能化、企业与用户的颗粒度越来越细&#xff0c;满足每个人的个性化要求也是未来商业的重要特征&#xff01;马云曾经说过&#xff0c;未来的核心资源是数据&#xff0c;数据将成为一家企业动力源&#xff0c;而这一切的基础都…

neo4j相同查询语句一次查询特慢再次查询比较快。

现象&#xff1a; neo4j相同查询语句一次查询特慢再次查询比较快。 分析&#xff1a; 查询语句 //查询同名方法match(path:Method) where id(path) in [244333030] and NOT path:Constructor//是rpc的方法match(rpc_method:Method)<-[:DECLARES]-(rpc_method_cls:Class) -…