『 实战项目 』Cloud Backup System - 云备份

文章目录

  • 云备份项目
    • 服务端功能
    • 服务端功能模块划分
    • 客户端功能
      • 客户端模块划分
    • 项目条件
    • Jsoncpp第三方库
    • Bundle第三方库
    • httplib第三方库
      • Request类
      • Response类
      • Server类
      • Client类
      • 搭建简单服务器
      • 搭建简单客户端
    • 服务端工具类实现 - 文件实用工具类
    • 服务器配置信息模块实现- 系统配置信息
    • 服务端配置信息模块实现 - 单例文件配置类设计
    • 服务端管理模块实现 - 需要管理的数据(后期需要用到的数据)
      • 服务端管理模块设计
    • 热点管理模块
    • 网络通信模块和业务处理模块
      • 网络通信接口设计
      • 服务端业务处理类的设计
      • 断点续传
    • 客户端
      • 数据管理模块
      • 数据管理类设计
      • 文件备份类设计
    • 项目源码


云备份项目

将本地指定目录中需要备份的文件进行上传,将文件上传至服务器当中;

且随时能通过浏览器进行查看与下载;

下载过程中支持断点续传(暂停后继续下载);

服务器对所上传文件进行热点管理:

  • 判定文件是否为热点文件

    热点文件的判定为在一定时间或者条件内访问该文件的此处或者是文件最后一次访问的时间来判断是否为热点文件;

    • 为热点文件

      不处理

    • 为非热点文件

      将非热点文件进行压缩处理以节省云服务器的磁盘空间;


服务端功能

  • 支持客户端文件的备份

  • 支持客户端浏览器的查看与下载

  • 支持客户端文件下载功能

    断点续传;

  • 热点文件管理

    对服务器中长时间无访问的文件进行压缩存储存储;


服务端功能模块划分

  • 数据管理模块

    管理的备份文件信息,以便于随时获取;

  • 网络通信模块

    实现与客户端的网络通信;

  • 业务处理模块

    负责上传,展示列表,下载(断点续传)功能;

  • 热点文件管理模块

    对长时间无访问文件进行压缩存储;


客户端功能

  • 指定文件夹中的文件检测

    获取文件夹中的文件;

  • 判断文件是否需要备份

    新增的文件,备份在服务端但被修改过的文件;

    除此之外间隔一段时间进行判断是否需要备份(如五分钟,十分钟);

  • 将需要备份的文件上传到服务器上


客户端模块划分

  • 数据管理模块

    管理备份文件信息;

  • 文件检测模块

    检测/监控指定的目录/文件夹;

    • 例:

      当一个文件被加载至文件夹中, 而数据管理模块中备份文件信息并没有该文件的信息则表示该文件为新加入的文件,需要进行备份;

      检测一个文件的信息与备份的文件信息是否一致,检测文件是否又被修改,若是文件被修改则需要进行备份;

  • 文件备份模块(网络通信模块)

    上传需要备份的文件数据,使得服务端存储所上传的数据;


项目条件

环境要求编译器gcc-c++ 7.3.1 以上支持;

  • 环境搭建:

    bundle数据压缩库,jsoncpp库,httplib第三方网络库;


Jsoncpp第三方库

数据交换格式,数据序列化与反序列化;

数据类型: 对象,数组,字符串,数字

  • 对象

    使用花括号{}括起来的表示为一个对象;

  • 数组

    使用中括号[]括起来的表示一个数组;

  • 字符串

    使用""常规双引号括起来表示一个字符串;

  • 数字

    直接使用,包括整形和浮点型;

假设一组数据以cpp的形式为:

/* Cpp */
char* name1 = "张三";
char* name2 = "李四";
int age1 = 18;
int age2 = 22;
float score1[3] = {88,89,90};
float score2[3] = {99,82.5,100}

对应json的形式则为:

[{"姓名" : "张三","年龄" : 18,"成绩" : [88,89,90]},{"姓名" : "李四","年龄" : 22,"成绩" : [99,82.5,100]}
]

Bundle第三方库

对文件进行压缩与解压缩;

为嵌入式第三方库,嵌入式表示需要将头文件源文件嵌入至项目内(静态库.h文件);

/*	使用例	  */#include <cassert>
#include "bundle.h"int main() {using namespace bundle;using namespace std;// 23 mb datasetstring original( "There's a lady who's sure all that glitters is gold" );for (int i = 0; i < 18; ++i) original += original + string( i + 1, 32 + i );// pack, unpack & verify all encodersvector<unsigned> libs { RAW, SHOCO, LZ4F, MINIZ, LZIP, LZMA20,ZPAQ, LZ4, BROTLI9, ZSTD, LZMA25,BSC, BROTLI11, SHRINKER, CSC20, BCM,ZLING, MCM, TANGELO, ZMOLLY, CRUSH, LZJB};for( auto &lib : libs ) {string packed = pack(lib, original);string unpacked = unpack(packed);cout << original.size() << " -> " << packed.size() << " bytes (" << name_of(lib) << ")" << endl;assert( original == unpacked );}cout << "All ok." << endl;
}

其中主要的操作为pack()unpack;

共有三个等级的API接口:

namespace bundle
{// low level API (raw pointers)bool is_packed( *ptr, len );bool is_unpacked( *ptr, len );unsigned type_of( *ptr, len );size_t len( *ptr, len );size_t zlen( *ptr, len );const void *zptr( *ptr, len );bool pack( unsigned Q, *in, len, *out, &zlen );bool unpack( unsigned Q, *in, len, *out, &zlen );// medium level API, templates (in-place)bool is_packed( T );bool is_unpacked( T );unsigned type_of( T );size_t len( T );size_t zlen( T );const void *zptr( T );bool unpack( T &, T );bool pack( unsigned Q, T &, T );// high level API, templates (copy)T pack( unsigned Q, T );T unpack( T );
}

主要的操作流程为:

  • 压缩
    • 创建两个文件流(读文件流和写文件流)
    • 用读文件流打开原文件(以二进制形式即std::ios::binary)
    • seekg()将文件流跳转至末尾并使用tellg()获取当前文件流位置(本意是获取偏移量来计算文件大小)最后再次使用seekg()将文件流跳回远处
    • 创建一个std::string字符串对象并使用文件大小来初始化字符串大小
    • 读文件流调用read()将文件数据写至字符串中
    • 字符串调用bundle::pack()传入字符串进行压缩同时将返回一个字符串std::string(Hight level API)
    • 用写文件流打开文件(以二进制形式即std::ios::binary)
    • 调用write()将压缩后的数据写入文件中作为压缩文件
    • 关闭两个文件流
  • 解压过程一致,只是调用接口不同,不进行赘述

httplib第三方库

第三方的网络库,一个C++11单头文件的跨平台HTTP/HTTPS库;

使用时包含httplib.h头文件即可;

使用第三方库能够使得更加轻松的搭建一个http服务器或者客户端从而提高开发效率;


Request类

Http请求格式通常为:

Request类为如下:

struct MultipartFormData {                 std::string name; // 字段名称std::string content; // 文件内容std::string filename; // 文件名称std::string content_type; // 正文类型};
using MultipartFormDataItems = std::vector<MultipartFormData>;struct Request {// HTTP method and pathstd::string method; // 请求方法std::string path; // 资源路径// Query parameters and headersParams params; // 查询字符串Headers headers; // 头部字段// Request bodystd::string body; // 正文部分// Remote and local address informationstd::string remote_addr;int remote_port = -1;std::string local_addr;int local_port = -1;// Server-specific attributesstd::string version; // 协议版本MultipartFormDataMap files; // 所保存的客户端上传文件信息(见首行)Ranges ranges;	// 用于实现断点续传的请求文件区间// 存在一个开始位置和结束位置// Header-related methodsbool has_header(const std::string &key) const; // 查询header中是否有哪个头部字段std::string get_header_value(const std::string &key, const char *def = "", size_t id = 0) const; // 获取头部字段的值void set_header(const std::string &key, const std::string &val); // 设置头部字段的值bool has_file(const std::string &key) const; // 是否包含某个文件MultipartFormData get_file_value(const std::string &key) const; // 获取文件信息
private:
// ...
};

客户端保存的所有http请求相关信息,将其组织成http请求报文并发送给服务器;

服务器收到http请求后进行解析,将解析的数据保存在Request结构体中;


Response类

http响应格式为:

Response类通常存储响应信息;

Response类为如下:

struct Response {std::string version; // 协议版本 - 不需要填充int status = -1;	// 响应状态码Headers headers;	// 头部字段std::string body;	// 响应给客户端的正文void set_header(const std::string &key, const std::string &val); // 设置头部字段void set_content(const std::string &s, const std::string &content_type); // 设置正文// ...
};
  • 功能:

    用户将响应数据放到结构体中,httplib将会其中的数据按照httpResopnse格式组织成为一条响应,并发送给客户端;


Server类

Server类用于搭建http服务器:

class Server {
public:using Handler = std::function<void(const Request &, Response &)>; // 函数指针类型 - 定义了一个http请求处理回调函数格式// httplib搭建的服务器收到请求后进行解析,得到一个Request结构体,其中包含请求数据// 根据请求数据从而可以处理这个请求 处理函数定义的格式就是Handler// Request参数保存请求数据 让用户能够根据请求数据进行业务处理// Response参数需要用户在业务处理中填充数据,最终将数据通过响应的方式返回给客户端// -----------using Handlers = std::vector<std::pair<std::regex, Handler>>;// Handlers 表示一个请求路由数组(请求与处理函数映射表)// 其中regex 是一个正则表达式 用于匹配http请求资源路径// Handler 为请求处理函数指针// 可以理解为 Handlers 是一张表,映射了一个客户端请求的资源路径和一个处理函数(用户自定义的函数)// 当服务器接收到了对应的请求后将会根据资源路径以及请求方法到这张表中查找是否有匹配的处理函数 若是没有匹配的处理函数则返回 404// -----------std::function<TaskQueue *(void)> new_task_queue;// 线程池 - 用于处理请求// 当httplib收到一个新的连接时 这个客户端连接将会被扔进线程池中;/* 线程池中线程的工作:1. 接收请求,解析请求,得到Request结构体2. 在Handlers映射表中,根据请求信息查找处理函数,如果有则调用处理函数 void(const Request &, Response &)3. 当处理函数调用完毕,根据函数返回的Response结构体中的数据组织http响应发回给客户端*/// -----------bool listen(const std::string &host, int port, int socket_flags = 0);// 搭建并启动 http 服务器// -----------// 下列函数为针对某种请求方法的// 其中 handler 可调用对象参数为需要传入的函数指针// 其中 pattern 表示资源路径 正则表达式的格式 Server &Get(const std::string &pattern, Handler handler);Server &Post(const std::string &pattern, Handler handler);Server &Put(const std::string &pattern, Handler handler);Server &Patch(const std::string &pattern, Handler handler);Server &Delete(const std::string &pattern, Handler handler);Server &Options(const std::string &pattern, Handler handler);};

Client类

struct MultipartFormData {                 std::string name; // 字段名称std::string content; // 文件内容std::string filename; // 文件名称std::string content_type; // 正文类型};
using MultipartFormDataItems = std::vector<MultipartFormData>;
class Client {
public:// Universal interfaceexplicit Client(const std::string &scheme_host_port); // 传入服务器的IP地址和端口// 下列接口都为客户端向服务端发送对应请求Result Get(const std::string &path, const Headers &headers); // 向服务端发送GET请求 参数分别传递请求路径和头部字段Result Post(const std::string &path, const char *body, size_t content_length, const std::string &content_type); // 向服务端发送 POST 请求, path 表示提交给哪个资源路径, body 表示正文数据, content_length 表示正文长度, content_type 表正文类型Result Post(const std::string &path, const MultipartFormDataItems &items); // 向服务端发送 POST 请求, path 表示资源提交路径, items 的类型为 MultipartFormDataItems, 实际上为一个vector数组;// POST 请求提交多区域数据, 常用于多文件上传
};

搭建简单服务器

搭建服务器类最简单的方式就是使用httplib第三方库实例化出一个对应的Server类服务器对象server;

通过httplib::Server::Get(),httplib::Server::Post()等成员函数将对应的请求与处理函数注册进服务器当中;

最后通过httplib::Server::listen()函数传入需要监听的IP和端口号,实例化并启动服务器;

#include <iostream>#include <regex.h>#include "httplib.h"void Hello(const httplib::Request  &req,httplib::Response &rsp){rsp.set_content("Hello world","text/plain"); // 设置响应正文 其中数据类型为正文类型 "text/plain"rsp.status = 200;   // 设置状态码为 200
}void Numbers(const httplib::Request &req, httplib::Response &rsp){auto num = req.matches[1]; // 位置[0]中保存的是整体path,往后的下标中保存的都是捕捉的数据rsp.set_content(num,"text/plain"); // 此处 num 获取上来仍为一个字符串 并未进行类型转移 因此直接以文本类型输出即可rsp.status = 200;
}void Multipart(const httplib::Request &req, httplib::Response &rsp){// 来打酱油的 混个眼熟 在客户端中再配auto ret = req.has_file("file"); // 判断传入的请求中是否传有名为 "file" 的文件if(ret == false) {// 表示不是文件上传std::cout<<"not file upload\n";rsp.status = 400;return ;}const auto &file = req.get_file_value("file"); // 获取文件区域数据信息rsp.body.clear();rsp.body = file.filename;   // 文件名称rsp.body += "\n";rsp.body += file.content;   // 文件内容rsp.set_header("Content-Type", "text/plain"); // 设置头部字段rsp.status = 200; // 设置状态码return ;}int main(){// std::cout<<"hello world"<<std::endl;httplib::Server server; // 实例化一个Server对象server.Get("/hi",Hello); // 注册一个针对"hi"的GET请求的处理函数映射关系// server.Get(R"(/number/(\d+))",Numbers); // 这两行内容大致相同 其中采用 R 来去除字符串中特殊含义 如转义字符 server.Get("/number/(\\d+)",Numbers); // vimplus 中的报错可以不用卵// ()用来捕捉数据// 这里是注册一个针对"/number/[具体数字]"的GET请求的处理函数映射关系// \d 是正则表达式 表示[0-9]的字符 // + 表示子表达式一次或多次 (>=1) server.Post("/multipart",Multipart);server.listen("0.0.0.0",8121); // 创建并使用服务器return 0;
}

运行服务器后浏览器可以正常访问(防火墙已经开启);


搭建简单客户端

客户端的搭建与服务器的搭建别无二致;

客户端只需要实例化出一个httplib::Client类型的客户端类并传入需要访问的服务器IP地址与端口号即可;

无需调用listen,当程序运行时即表示对服务器进行访问;

在客户端中可以直接调用对应的成员函数,如httplib::Client::Get(),httplib::Client::Post()函数来进行对应的操作,如上传某个资源或者获取某个资源;

#include <iostream>
#include "httplib.h"  // 引入 httplib 库的头文件,用于 HTTP 请求处理
#include <vector>    // 引入 vector 容器,用于存储 multipart 文件条目// 定义服务器地址和端口号
static const char* SERVER_IP = "114.55.52.91"; // 目标服务器的 IP 地址
static const uint16_t SERVER_PORT = 8121;     // 目标服务器对应的端口号int main() {// 创建一个 HTTP 客户端,设置目标服务器的 IP 和端口httplib::Client client(SERVER_IP, SERVER_PORT);// 定义一个 MultipartFormData 对象,用于存储文件字段信息httplib::MultipartFormData item;item.name = "file";             // 字段名称为 file,对应表单中的 key 值item.filename = "hello.txt";    // 设置文件名称为 hello.txtitem.content = "Hello world";   // 设置文件内容为 Hello worlditem.content_type = "text/plain"; // 指定文件的 MIME 类型为纯文本类型 (text/plain)// 创建一个列表(容器),用于存放多个 MultipartFormData 对象httplib::MultipartFormDataItems items;items.push_back(item);          // 将上述文件数据结构添加到列表中// 向服务器发送 POST 请求到路径 /multipart,并附加 multipart 文件数据auto res = client.Post("/multipart", items);// 打印服务器返回的 HTTP 状态码和响应内容if (res) {  // 检查请求是否成功 (res 是否为 nullptr)std::cout << "HTTP Status Code:  "<< res->status << std::endl;   // 输出状态码std::cout << "Response Body:  "<< res->body << std::endl;       // 输出响应主体} else {  // 如果请求失败std::cerr << "Error: Failed to connect to server! "<< std::endl;}return 0;
}

该案例中使用了httplib::MultipartFormData来定义一个文件数据结构并上传对应的文件信息;

结合上文服务器构建;

运行程序结果如下:


服务端工具类实现 - 文件实用工具类

设计一个封装文件操作类,用于简化后续任意模块对文件的操作;

  • 结构大致如下:

    class {private:std::string _filename; // 文件名(包含路径)public:size_t FileSize(); // 获取文件大小time_t LastMTime(); // 获取文件最后一次修改时间time_t LastATime(); // 获取文件最后一次访问时间 (通过访问时间判断是否为热点文件)std::string FileName(); // 获取文件路径名中的文件名称bool SetContent(const std::string &body); // 向文件内写数据(write)bool GetContent(std::string *body); // 从文件中读取数据(read)bool GetPoslen(std::string *body, size_t pos, size_t len); // 获取文件指定位置 指定长度的数据 (断点续传)bool Exists(); // 判断文件(或目录)是否存在bool CreateDirectory(); // 创建目录bool GetDirectory(std::vector<std::string> *arry); // 遍历目录(目录也是文件) 获取目录中所有文件的文件名(包括路径)bool Compress(const std::string &packname); // 进行热点管理时的压缩bool UnCompress(const std::string &packname); // 解压缩
    };
    

服务器配置信息模块实现- 系统配置信息

  • 热点判断时间

    判断多长时间没有访问的文件为非热点文件;

  • 文件下载的URL前缀路径

    采用Web根目录的方式,如wwwroot的方式配置web根目录使得避免客户端可通过路径直接访问到服务器的其他非web资源;

    同时通过区别前缀路径判别不同请求,如:

    http://127.0.0.1:8080/download/downloadfile.txt
    

    其中/download这个前置路径表示这是一个下载请求,下载的文件是downloadfile.txt文件;

  • 压缩包后缀名称

    在对一个文件进行压缩后,这个文件压缩后的文件名为原文件名+对应的压缩格式;

    如在压缩模块中采用LZIP形式进行压缩;

    假设对一个为downloadfile.txt文件进行压缩,压缩后的文件名则为downloadfile.txt.lz;

  • 上传文件存放路径

    一个文件上传后在云服务器中的对应位置;

    在上面提到以wwwroot作为web根目录,上传文件放在哪一个区域;

  • 压缩文件存放路径

    与上传文件相似,当服务器检测到一个文件由热点文件变为非热点文件状态后将要对文件进行压缩,压缩后将要与未压缩文件进行区分,将其单独放在一个列表中进行管理;

  • 服务端备份信息存放文件

    当客户端上传一个文件后服务端需要对上传的文件信息进行备份,这些信息包括文件大小,文件名,最后修改时间,最后访问时间等等;

    这些信息将单独存放在一个文件中方便进行查找;

  • 服务器的访问IP地址

  • 服务端的访问端口


服务端配置信息模块实现 - 单例文件配置类设计

class Config{
public:static COnfig *GetInstance(); // 获取单例
private:Config(){} // 构造函数私有化 并且在构造函数中读取配置文件static std::mutex _mutex; // 互斥锁 - 搞定同步问题static Config *_instance; // 单例实例
private:int _hot_time; // 热点时间判断int _server_port; // 服务器监听端口std::string _download_prefix; // 下载url前缀路径std::string _packfile_suffix; // 压缩包后缀名称std::string _back_dir; // 备份文件存放目录std::string _pack_dir; // 压缩包文件存放目录std::string _server_ip; // IP地址std::string _backup_file; // 数据信息存放文件    
public:int GetHotTime(); // 获取热点时间int GetServerPort(); // 获取服务器监听端口std::string GetDownloadPrefix(); // 获取前置路径std::string GetPackFileSuffix(); // 获取压缩包std::string GetBackDir(); // 获取备份文件目录std::string GetPackDir(); // 获取压缩包文件目录std::string GetServerIP(); // 获取IP地址std::string GetBackupFile(); // 获取数据信息存放文件};

服务端管理模块实现 - 需要管理的数据(后期需要用到的数据)

  • 下载相关

    • 文件的实际存储路径

      当客户端需要下载文件时需要从哪个路径哪个文件中读取数据响应下载;

    • 文件压缩包存放路径

      被判为非热点文件的文件将被压缩存储,届时被查看后需要进行解压,解压位置在哪个位置;

      对应的当客户端进行下载时也同样要把文件进行解压并传输,压缩文件路径在哪;

    • 文件是否被压缩标志位

      判断文件是否被压缩;

  • 文件属性相关

    • 文件大小

    • 文件最后一次修改时间

    • 文件最后一次访问时间

    • 文件访问URL中资源路径path

      如:

      /download/a.txt
      
  • 如何管理数据

    • 用于数据信息访问

      使用哈希表在内存中进行数据管理,以urlpath作为key值;

    • 持久化存存储管理

      使用json序列化将所有数据信息保存在文件中;


服务端管理模块设计

  • 数据信息结构体

    typedef struct BackupInfo_t{bool pack_flag;// 判断文件是否被压缩size_t fsize; // 文件大小time_t atime; // 最后一次访问时间time_t mtime; // 最后一次修改时间std::string real_path; // 文件实际存储路径名称std::string pack_path; // 压缩包存储路径名称std::string url_path; // 请求资源路径
    }BackupInfo;
    
  • 数据管理类

    class DataManager{private:DataManager();std::string _backup_file; // 持久化存储文件std::unordered_map<std::string, BackupInfo> _table; // 数据信息结构体在内存中以 hash 表存储pthread_rwlock_t _rwlock; // 读写锁 引入线程池后使得多个线程能够一起读 写时互斥bool Storage(); // 持久化存储 - 每当数据新增或者修改时都要重新持久化存储以避免数据丢失bool InitLoad(); // 初始化加载, 在每次系统重启后都要加载以前的数据bool Insert(const BackupInfo &info); // 新增 - 将BackupInfo存储进哈希表中 以 k-v 的形式存储bool Update(const BackupInfo &info); // 当一个文件被压缩后可能需要修改某些信息 如:bool pack_flag;bool GetOneByUrl(const std::string &url, BackupInfo *info); // 当客户端发起下载请求时需要获取对应的文件信息bool GetOneByRealpath(const std::string &path, BackupInfo *info); // 根据真实路径来获取文件信息 判断其是否为一个非热点文件等信息bool GetAll(std::vector<BackupInfo> *arry); // 获取所有信息
    };
    
  • 获取信息函数

    void NewBackupInfo(const std::string realpath, BackupInfo *info); // 可作为 BackupInfo 的成员函数
    

    通过所传入的路径,将路径对应的文件中的信息在函数中填充至info中;


热点管理模块

对服务器上备份的文件进行检测,检测哪些文件长时间没有被访问则认为是非热点文件进行压缩存储从而节省磁盘空间;

实现思路为:

遍历所有的文件,检测文件最后一次访问时间,与当前时间进行相减得到差值;

这个差值如果大于设定好的非热点判断时间则认为是非热点文件;

  • 遍历所有文件

    • 从数据管理模块中遍历所有备份文件信息
    • 遍历备份文件夹,获取所有文件进行属性获取从而判断

    选择遍历备份文件夹从而获取最新的数据;

    数据管理模块中的数据不一定是最新的,并且遍历文件夹同时可以解决数据信息缺漏的问题;

  1. 遍历备份目录 获取所有文件路径名称
  2. 逐个文件获取最后一次访问时间与当前系统时间进行比较判断
  3. 对非热点文件进行压缩处理并删除源文件
  4. 修改数据管理模块对应的文件信息(压缩标志 -> true)

流程为如下:

  • 获取备份目录下的所有文件
  • 逐个判断文件是否为非热点文件
  • 非热点文件压缩处理
  • 删除源文件 修改备份信息
extern DataManager* _data; // 数据管理模块 class HotManager{
private:std::string _back_dir; // 备份文件路径std::string _pack_dir; // 压缩文件路径std::string _pack_suffix; //压缩包后缀名int _hot_time; // 热点判断时间
public:HotManager();bool RunModule(); // 模块启动
};

网络通信模块和业务处理模块

网络模块可以直接使用httplib实现,因此具体只需要处理业务处理即可;

  • 搭建网络通信服务器

    借助httplib完成;

  • 业务请求处理

    • 文件上传请求

      备份客户端上传的文件,响应上传成功;

    • 文件列表请求:

      客户端浏览器请求一个备份文件的展示页面,响应页面;

    • 文件下载请求

      通过展示页面,点击下载,响应客户端要下载的文件数据;


网络通信接口设计

约定好客户端发送什么样的请求,服务端返回什么样的响应;

  • 请求

    • 文件上传
    • 展示页面
    • 文件下载
  • 接口设计

    接口设计为上述三种请求对应的响应接口;

    • 文件上传

      当客户端发送了一个POST方法的"/upload"请求时表示是一个上传请求;

      解析请求得到文件,将数据写入到文件中;

      并返回一个对应的响应:

      上传成功:

      HTTP/1.0 200 OK
      

      上传失败例:

      HTTP/1.1 500 Bad Request
      
    • 页面展示

      当客户端发送了一个GET方法的"/listshow"请求时表示是一个页面请求;

      对应的响应可能为:

      HTTP/1.1 200 OK
      Content-Length: 头部信息...<html>...</html>正文信息 <!-- 页面信息 -->
      
    • 文件下载

      当客户端发送了一个GET方法的"/download/filename"请求时表示为下载一个名为filename文件;

      对应的响应可能为:

      HTTP/1.1 200 OK
      Content-Length: 文件长度等头部信息正文数据(文件数据)
      

服务端业务处理类的设计

class Service{
// 搭建一个http服务器并且进行业务处理
private:int _server_port; // 服务器端口 - 可从配置文件获取std::string _server_ip; // 服务器IP - 可从配置文件获取std::string _download_prefix; // 下载前缀 - 可从配置文件获取httplib:Server _server; // 构建服务器
private:void Upload(const httplib::Request &req, httplib::Response &rsp); // 上传业务处理void ListShow(const httplib::Request &req, httplib::Response &rsp); // 页面展示业务处理void Download(const httplib::Request &req, httplib::Response &rsp); // 下载请求业务处理
public:Server(); // 构造函数 初始化服务器所需资源bool RunModule(); // 运行服务器 - 业务处理
};
  • 文件上传

    在进行文件上传时对应的客户端将以约定好的表单字段"file"判断是否为一个文件;

    将文件使用FileUtil工具写入至back_dir备份路径中;

    并调用NewBackupInfo()填充BackupInfo信息插入至_data中;

  • 页面展示

    需要展示一个html界面;

    获取所有的文件信息BackupInfo,组织前端信息为字符串;

    返回字符串的前端信息;

  • 文件下载

    文件下载的思路为采用http协议的ETag字段(存储一个资源的唯一标识);

    客户端第一次下载的时候会收到这个响应信息,第二次下载的时候将会把信息发送给服务器,想要让服务器根据这个唯一标识判断资源是否又被修改,如果未被修改则直接使用原先缓存的数据,无需重新下载;

    此处的ETag使用"文件名-文件大小-最后修改时间"组成(ETag字段信息是什么http协议并不关心,服务端能够自己标识即可);


断点续传

当文件下载过程中因为某种异常导致中断,如果再次从头下载将会降低效率;

因为已经传输过的数据需要再传输一次;

断点续传是在上次下载断开的位置继续下载,已经传输过的数据将不需要重新传输;

  • 目的

    提高文件重新传输效率;

  • 思想

    客户端在下载文件时需要时刻记录当前下载数据量;

    当异常下载中断时下次断点续传需要将重新下载的数据区间(起始位置与当前位置)发送给服务器;

    服务器接收后仅回传客户端剩余数据;

    异常下载中断后的重新下载需要判断源文件是否有被修改而判断是否需要重新下载;

    如果源文件已经被修改那就需要重新下载,因为已经下载下来的数据可能已经被修改了是作废数据;

在客户端发起第一次下载请求时,服务端向客户端返回响应时需要返回对应的ETag字段;

当出现异常后客户端再次向服务端发起下载请求,此时的下载请求将会携带 If-Range 头部字段;

If-Range头部字段为服务端在下载时响应的etag字段;

同样断点续传的下载请求还会携带头部字段Range: bytes [N]-[M];

这个表示客户端需要的数据区间为[N]-[M];

  • 服务端动作

    判断If-RangeETag字段是否相同,不同则从头将文件进行传输;

    相同则返回[N]-[M]的数据;

    当进行断点续传时,即If-RangeETag字段相同时对应的返回状态码不再是200,而应该是206,表示服务端处理了部分GET请求(Partial Content);

    • 响应

      HTTP/1.1 206 Partial Content
      ETag: "xxxxxxxxxx"
      Content-Range: bytes [N]-[M]/文件大小[数据正文]正文即为对应区间数据
      

      其中Range:字段有几种形式:

      bytes start-end   # 从头到结束
      bytes 100-200	  # 从100byte-200byte
      bytes 100-		  # 从100byte到结束
      

客户端

自动对指定文件夹进行备份;

  • 模块划分

    • 数据管理模块

      管理备份文件信息;

    • 目录遍历

      获取指定文件夹中的所有文件路径名;

    • 文件备份模块

      将需要备份的文件上传至服务器;

开发环境为Windows11,采用2017以上版本的VisualStudio作为集成开发环境(支持C++17);

  • 同样采用与服务器相同的 FileUtil

    将服务端中设计的util.hpp文件进行拷贝至当前项目目录中;

    由于在客户端中未使用jsoncpp第三方库,因此可以将对应JsonUtil所封装的内容进行删除;

    同时客户端无需操作压缩Compress和解压UnCompress,应对应功能进行删除;

    所使用的VisualStudio可能提示已经在C++17中摒弃了#include <experimental/filesystem>需要将该头文件改成#include <filesystem>;

    可以使用下列#define进行定义以防止编译时的报错:

    #define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING 1
    

数据管理模块

数据管理模块用于管理备份文件信息;

判断一个文件是否需要进行备份(或重新备份);

  • 文件是否为新增
  • 若是文件不为新增则判断该文件是否为已备份文件的修改版

主要管理的数据为 “文件路径名”“文件唯一标识” ;

  • 实现思想

    • 内存存储

      高效率访问,使用哈希表(unordered_map);

    • 持久化存储(文件存储)

      文件存储涉及到数据序列化与反序列化(不采用jsoncpp,自定义序列化格式);

      key value\n

      key为文件路径名,value为文件唯一标识(判断文件上传后是否被修改);


数据管理类设计

客户端的数据管理类与服务端大致相同(本质上都为数据管理);

	class DataManager {private:std::unordered_map<std::string, std::string> _table; // 用于快速访问std::string _backup_file; // 备份信息文件private:bool InitLoad(); // 读取已有备份信息public:DataManager(std::string);bool Insert(const std::string& , const std::string&); // 插入新的备份信息至哈希表bool Update(const std::string&, const std::string&); // 更新哈希表内的某个文件备份信息bool Storage(); // 持久化存储bool GetOneByFname(const std::string&, std::string*); // 获取一个文件的唯一标识符int Split(const std::string&, const std::string&, std::vector<std::string>*); // 用于进行反序列化的字符串分割}; // calss DataManager

文件备份类设计

自动将指定文件夹中的文件备份到服务器;

  • 遍历指定文件夹获取文件信息
  • 注意判断文件是否需要被备份
  • 需要备份的文件进行上传至服务器备份
#define SERVER_ADDR "xxx.xxx.xxx.xxx" /* IP地址 */ 
#define SERVER_PORT 8888 /* 端口号 */
class Backup{
private:std::string _back_dir; // 需要备份的文件夹DataManager *_data; // 用于获取文件信息 判断文件是否需要被备份std::string GetFileIDentifier(const std::string &filename); // 计算获取文件唯一标识bool Upload(); // 当判断文件需要进行备份上传时则调用Upload进行备份上传public:Backup(std::string &backdir, std::string &backup_file); //构造函数(需要传入指定文件夹作为备份文件夹)bool RunModule(); // 用来运行文件备份模块的主要功能(上述功能)bool IsNeedUpload(const std::string &filename);// 判断文件是否需要被上传 (判断文件是否有被修改/文件上一次的修改时间是否过近 如果修改时间过近则可能表示文件正在实时拷贝当中 不适合上传)
};

项目源码

  • gitee

    Gitee - 半介莽夫/CloudSystem

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

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

No.36 学习 | Python 函数:从基础到实战

最近我在学 Python 编程&#xff0c;今天可算是狠狠钻研了一把 Python 里的函数&#xff0c;感觉脑袋里的知识又充实了不少&#xff0c;赶紧来记一记。 一、Python函数基础概念 &#xff08;一&#xff09;pass语句&#xff1a;代码块的“占位符” 在编写代码时&#xff0c;有…

easyexcel读取写入excel easyexceldemo

1.新建springboot项目 2.添加pom依赖 <name>excel</name> <description>excelspringboot例子</description><parent> <groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-parent</artifactId&…

Qt 5.14.2 学习记录 —— 십유 布局管理器

文章目录 1、QVBoxLayout2、QHBoxLayout3、QGridLayout4、QFormLayout5、QSpacerItem 布局管理器是为了让程序员不需要自己决定控件的绝对位置&#xff0c;而是通过布局管理器方便地放置 1、QVBoxLayout 垂直布局管理器 #include <QPushButton> #include <QVBoxLayo…

Markdown Viewer 浏览器, vscode

使用VS Code插件打造完美的MarkDown编辑器&#xff08;插件安装、插件配置、markdown语法&#xff09;_vscode markdown-CSDN博客 右键 .md 文件&#xff0c;选择打开 方式 &#xff08;安装一些markdown的插件) vscode如何预览markdown文件 | Fromidea GitCode - 全球开发者…

每日十题八股-2025年1月23日

1.快排为什么时间复杂度最差是O&#xff08;n^2&#xff09; 2.快排这么强&#xff0c;那冒泡排序还有必要吗&#xff1f; 3.如果要对一个很大的数据集&#xff0c;进行排序&#xff0c;而没办法一次性在内存排序&#xff0c;这时候怎么办&#xff1f; 4.面试官&#xff1a;你的…

H3C-无线WLAN配置案例(二层隧道转发)

目录 1.无线wlan产生背景:2.网络拓扑:3.网络简述:4.网络配置:4.1 网络基础配置4.2 无线wlan二层隧道转发配置4.3 无线wlan验证: 1.无线wlan产生背景: 无线WLAN&#xff08;无线局域网&#xff09;的产生背景主要源于以下几个方面的需求和技术发展&#xff1a;移动性和便捷性需…

HarmonyOS Next构建工具 lycium 原理介绍

HarmonyOS Next构建工具 lycium 原理介绍 背景介绍 HarmonyOS Next中很多系统API是以C接口提供&#xff0c;如果要使用C接口&#xff0c;必须要使用NAPI在ArkTS与C间交互&#xff0c;这种场景在使用DevEco-Studio中集成的交叉编译工具&#xff0c;以及cmake构建工具就完全够用…

设计模式的艺术-职责链模式

行为型模式的名称、定义、学习难度和使用频率如下表所示&#xff1a; 1.如何理解职责链模式 最常见的职责链是直线型&#xff0c;即沿着一条单向的链来传递请求。链上的每一个对象都是请求处理者&#xff0c;职责链模式可以将请求的处理者组织成一条链&#xff0c;并让请求沿着…

js学习笔记(2)

一、函数 1.JavaScript 函数语法 函数就是包裹在花括号中的代码块&#xff0c;前面使用了关键词 function&#xff1a; function functionname() {// 执行代码 } 当调用该函数时&#xff0c;会执行函数内的代码。 可以在某事件发生时直接调用函数&#xff08;比如当用户点…

洛谷刷题1-3

比较巧妙&#xff0c;求最小公倍数&#xff0c;看多少个数一次循环&#xff0c;直接求解就好了&#xff0c;N的数量级比较大&#xff0c;一层循环也会超时&#xff0c;也用了点双指针的想法&#xff08;归并排序&#xff09; 这里很大的问题&#xff0c;主要是cin输入的时候遇到…

2025年数学建模美赛:A题分析(1)Testing Time: The Constant Wear On Stairs

2025年数学建模美赛 A题分析&#xff08;1&#xff09;Testing Time: The Constant Wear On Stairs 2025年数学建模美赛 A题分析&#xff08;2&#xff09;楼梯磨损分析模型 2025年数学建模美赛 A题分析&#xff08;3&#xff09;楼梯使用方向偏好模型 2025年数学建模美赛 A题分…

云原生时代,如何构建高效分布式监控系统

文章目录 一.监控现状二.Thanos原理分析SidecarQuerierStoreCompactor 三.Sidecar or ReceiverThanos Receiver工作原理 四.分布式运维架构 一.监控现状 Prometheus是CNCF基金会管理的一个开源监控项目&#xff0c;由于其良好的架构设计和完善的生态&#xff0c;迅速成为了监控…

每天五分钟深度学习pytorch:基于VGG神经网络完成CAFIR10的识别

本文重点 前面的所有模型我们都是使用VGG跑了mnist数据集,本次我们换一个数据集,我们使用CAFIR数据集,这个数据集我们前面介绍过,它和mnist不一样了,mnist是灰度图,这个是彩色图,所以它的通道数是3,这样我们再构建卷积神经网路的时候,第一个卷积层的输入通道数就应该…

力扣707题(2)——设计链表

#题目 #3,5和6的代码 今天看剩下几个题的代码&#xff0c;1,2,4的代码已经在上篇博客写过了想看的小伙伴移步到&#xff1a; 力扣707题——设计链表-CSDN博客 //第3题头插法 void addAtHead(int val){ //记录头结点ListNode nhead; //新节点的创建,并让它指向原本头结点的后…

STM32_SD卡的SDIO通信_基础读写

本篇将使用CubeMXKeil, 创建一个SD卡读写的工程。 目录 一、SD卡要点速读 二、SDIO要点速读 三、SD卡座接线原理图 四、CubeMX新建工程 五、CubeMX 生成 SD卡的SDIO通信部分 六、Keil 编辑工程代码 七、实验效果 实现效果&#xff0c;如下图&#xff1a; 一、SD卡 速读…

CPU 缓存基础知识

并发编程首先需要简单了解下现代CPU相关知识。通过一些简单的图&#xff0c;简单的代码&#xff0c;来认识CPU以及一些常见的问题。 目录 CPU存储与缓存的引入常见的三级缓存结构缓存一致性协议MESI协议缓存行 cache line 通过代码实例认识缓存行的重要性 CPU指令的乱序执行通过…

【博客之星】年度总结:在云影与墨香中探寻成长的足迹

&#x1f407;明明跟你说过&#xff1a;个人主页 &#x1f516;行路有良友&#xff0c;便是天堂&#x1f516; 目录 一、年度回顾 1、创作历程 2、个人成长 3、个人生活与博客事业 二、技术总结 1、赛道选择 2、技术工具 3、实战项目 三、前景与展望 1、云原生未来…

2024 自主创业事业小结和2025展望

一 2024创业事业小结&#xff1a; 1.1 2024 自主创业项目小结&#xff1a; 2024年我们小团队主要做了两大类的项目&#xff1a; 1&#xff0c;工业类 在工业领域的项目&#xff0c;我们做了3个落地的视觉集成项目。 1.1 旋转角度的测量&#xff1a; 由于是外包项目&#…

Redis使用基础

1 redis介绍 Redis&#xff08;Remote Dictionary Server )&#xff0c;即远程字典服务 ! 是完全开源的&#xff0c;遵守 BSD 协议&#xff0c;是一个高性能的 key-value 数据库。 使用ANSI C语言编写、支持网络、可基于内存亦可持久化的日志型、Key-Value数据库&#xff0c;并…

激光雷达和相机早期融合

通过外参和内参的标定将激光雷达的点云投影到图像上。 • 传感器标定 首先需要对激光雷达和相机&#xff08;用于获取 2D 图像&#xff09;进行外参和内参标定。这是为了确定激光雷达坐标系和相机坐标系之间的转换关系&#xff0c;包括旋转和平移。通常采用棋盘格等标定工具&…