云备份项目:在云端保护您的数据【二、开发】

在这里插入图片描述
☘️过度的信息对一个过着充实生活的人来说,是一种不必要的负担☘️

文章目录

  • 前言
  • 工具类实现
    • 文件实用工具类
      • 代码实现
    • Json实用工具类
      • 代码实现
  • 服务端
    • 单例配置类
      • 系统配置信息
      • 单例配置类
    • 数据管理类
      • 数据信息
      • 数据管理
    • 热点管理类
    • 业务处理类
  • 客户端
    • 数据管理类
    • 文件备份类
  • 总结


☘️项目源代码:云备份
☘️云备份专栏:云备份


前言

在云备份开篇的博客中介绍了云备份项目的总体实现方向,接下来就是对项目的逐步实现,然后对各部分进行组装。

在这里插入图片描述

工具类实现

在这里插入图片描述

如上的模块功能划分图,我们知道无论是客户端还是服务端都涉及对文件的操作,例如客户端需要将备份的文件上传至服务器时,就需要打开文件读取文件的数据然后将其进行组织成一定格式,在发送给服务器。

而服务器在接收到数据后,则需要将组织后的数据进行反序列化成原始数据,然后存放进文件中。当用户需要将备份的文件进行还原时,服务器也需要读取对应文件的数据,将其组织后再发送给客户端,客户端在进行反序列化得到原始数据后在写进文件。

在这里插入图片描述

因此,我们的工具类就需要有对文件操作的类以及对数据进行组织的类,分别为文件实用工具类Json实用工具类

文件实用工具类

我们在实现文件实用工具类时,使用了C++17的<filesystem>库,<filesystem> 库是 C++17 新增的标准库之一,旨在提供对文件系统进行操作的接口,使得文件和目录的处理更加简单和可移植。这个库定义了一组类和函数,可以用来进行文件和目录的操作,如路径处理、文件检查、目录遍历、文件复制、移动和删除等。

如下是 <filesystem> 库的主要组成部分和功能:

  1. 命名空间和别名: <filesystem> 库的内容定义在 std::filesystem 命名空间中。通常为了方便使用,可以使用 namespace fs = std::filesystem; 这样的别名来简化调用。

  2. 路径处理: <filesystem> 提供了丰富的路径处理功能,包括构建路径、连接路径、获取路径的各种组成部分等。

  3. 文件和目录检查: 通过 <filesystem> 可以方便地检查文件和目录的存在性、类型、权限等信息。

  4. 目录遍历: 提供了对目录进行遍历的功能,可以轻松地获取目录中的所有文件和子目录。

  5. 文件复制、移动和删除: 提供了对文件进行复制、移动和删除的接口,可以方便地进行文件操作。

  6. 文件大小和最后修改时间: 可以获取文件的大小和最后修改时间等属性信息。

  7. 文件权限: 可以设置和查询文件的权限,包括读、写和执行权限等。

  8. 异常处理: <filesystem> 提供了一些异常类,用于处理文件系统操作可能遇到的异常情况。

使用 <filesystem> 库可以简化很多文件和目录操作的代码,同时也提高了代码的可移植性,因为这个库提供的接口是标准化的,不会受到操作系统的影响。

在使用 <filesystem> 库时,需要确保编译器支持 C++17 标准,并在编译时指定相应的标准版本。例如,可以使用 -std=c++17-std=c++20 等标志来编译支持 <filesystem> 库的代码。

更多详细信息可以直接跳转filesystem。

代码实现

#include <iostream>
#include <string>
#include <vector>
#include <sys/stat.h>
#include <fstream>
#include <experimental/filesystem>
#include "bundle.h"namespace wzh
{namespace fs = std::experimental::filesystem;class FileUtil{public:FileUtil(const std::string filename): _filename(filename) {}int64_t fileSize() //获取文件大小{struct stat st;if(stat(_filename.c_str(), &st) < 0){std::cout << "get file size failed\n";return -1;}return st.st_size;}time_t lastModTime() //获取文件最后一次修改时间{struct stat st;if(stat(_filename.c_str(), &st) < 0){std::cout << "get file lastModTime failed\n";return -1;}return st.st_mtime;}time_t lastAccTime() //获取最后一次访问时间{struct stat st;if(stat(_filename.c_str(), &st) < 0){std::cout << "get file lastAccTime failed\n";return -1;}return st.st_atime;}std::string fileName() //获取文件名称{size_t pos = _filename.find_last_of("/");if(pos == std::string::npos){return _filename;}return _filename.substr(pos + 1);}bool setContent(const std::string &body) //向文件写入数据{std::ofstream ofs;ofs.open(_filename, std::ios::binary);if(ofs.is_open() == false){std::cout << "write open failed\n";return false;}ofs.write(&body[0], body.size());if(ofs.good() == false){std::cout << "write file content failed\n";ofs.close();return false;}ofs.close();return true;}bool getContent(std::string *body) //向文件读取数据{size_t fsize = fileSize();return getPosLen(body, 0, fsize);}bool getPosLen(std::string *body, size_t pos, size_t len) //获取文件指定位置,指定长度的数据{std::ifstream ifs;ifs.open(_filename, std::ios::binary);if(ifs.is_open() == false){std::cout << "read open file failed\n";return false;}size_t fsize = fileSize();if(pos + len > fsize){std::cout << "get file len is error\n";return false;}ifs.seekg(pos, std::ios::beg);body->resize(len);ifs.read(&(*body)[0], len);if(ifs.good() == false){std::cout << "get file content failed\n";ifs.close();return false;}ifs.close();return true;}bool exits() // 判断(文件/目录)是否存在{return fs::exists(_filename);     }bool scanDirectory(std::vector<std::string> *arry) //浏览目录中的所有文件{for(auto& p : fs::directory_iterator(_filename)){if(fs::is_directory(p) == true)continue;arry->push_back(fs::path(p).relative_path().string());} }bool createDirectory() //创建目录{if(exits()) return true;return fs::create_directories(_filename);}bool comPress(const std::string &packname) //压缩文件{std::string body;if(getContent(&body) == false){std::cout << "compress get file content failed\n";return false;}std::string packed = bundle::pack(bundle::LZIP, body);FileUtil fu(packname);if(fu.setContent(packed) == false){std::cout << "comPress write packed data failed\n";return false;}return true;}bool unCompress(const std::string &filename) //解压文件{std::string body;if(getContent(&body) == false){std::cout << "unCompress get file content failed\n";return false;}std::string unpacked = bundle::unpack(body);FileUtil fu(filename);if(fu.setContent(unpacked) == false){std::cout << "unCompress set file ccontent failed\n";return false;}return true;}private:std::string _filename;};
}

文件实用工具类 FileUtil,封装了一些常见的文件和目录操作功能。代码中定义了一个命名空间,用于组织代码。同时使用了别名定义,简化了文件系统命名空间的使用,使得可以使用 fs 来代替 std::experimental::filesystem

成员函数中,构造函数接受文件名作为参数,并初始化私有成员 _filename。而 fileSize()、lastModTime()、lastAccTime() 通过调用 stat 函数获取文件的大小、最后修改时间和最后访问时间。fileName() 用于从文件路径中提取文件名。setContent()、getContent()、getPosLen() 用于向文件写入数据、读取数据,以及获取指定位置和长度的文件内容。exits() 用于判断文件是否存在。scanDirectory() 遍历目录中的所有文件,并将文件名存储在传入的字符串向量中。createDirectory() 用来创建目录,如果目录已存在,则直接返回成功。comPress() 读取文件内容并使用 bundle 命名空间的 pack 函数进行压缩,然后将压缩后的内容写入新文件。unCompress() 读取文件内容并使用 bundle 命名空间的 unpack 函数进行解压,然后将解压后的内容写入新文件。

请注意,由于使用了 <experimental/filesystem> 头文件,编译器需要开启 C++17 或更高的标准支持,并且在链接时需要链接 <stdc++fs> 库。

Json实用工具类

对数据进行组织的Json实用工具类我们借助 JsonCpp 库来实现数据的处理。

代码实现

 class JsonUtil{public:static bool serialize(const Json::Value &root, std::string *str){Json::StreamWriterBuilder swb;std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());std::stringstream ss;if(sw->write(root, &ss) != 0){std::cout << "serialize write failed\n";return false;}*str = ss.str();return true;}static bool deSerialize(const std::string &str, Json::Value *root){Json::CharReaderBuilder crb;std::unique_ptr<Json::CharReader> cr(crb.newCharReader());std::string err;bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), root, &err);if(ret == false){std::cout << "deSerialize parse error: " << err << std::endl;return false;}return true;}};

JsonUtil 类中包含了两个静态方法,用于 JSON 数据的序列化和反序列化。serialize 方法接受一个 Json::Value 类型的参数 root,表示要序列化的 JSON 数据的根节点。还接受一个 std::string 指针参数 str,用于存储序列化后的 JSON 字符串。使用了 Json::StreamWriterBuilder 创建一个写入流构建器,然后创建一个唯一指针指向 Json::StreamWriter 对象。使用 sw->write 方法将 root 中的 JSON 数据写入一个 std::stringstream 中。如果写入失败,输出错误信息并返回 false,否则将 std::stringstream 中的内容赋值给传入的 str

deSerialize 方法接受一个 std::string 类型的参数 str,表示要反序列化的 JSON 字符串。还接受一个 Json::Value 指针参数 root,用于存储反序列化后的 JSON 数据。使用 Json::CharReaderBuilder 创建一个字符读取流构建器,然后创建一个唯一指针指向 Json::CharReader 对象。 cr->parse 方法将 str 中的 JSON 字符串解析为 root 中的 JSON 数据。如果解析失败,输出错误信息和错误内容,并返回 false,否则返回 true 表示反序列化成功。

Json::StreamWriterBuilderJson::CharReaderBuilder 是 JsonCpp 提供的用于构建写入流和字符读取流的类。Json::StreamWriterJson::CharReader 则是实际的写入和读取 JSON 数据的类。

这个工具类简化了使用 JsonCpp 库进行 JSON 数据的序列化和反序列化的过程,提供了方便的接口,同时对错误进行了基本的处理。在使用时,需要确保 JsonCpp 库被正确引入,并在编译时链接相应的库。

服务端

单例配置类

单例配置类确保系统在第一次启动时获取系统的配置信息,如热点文件的判断时间,间隔多久时间的文件再没有被访问时定义为非热点文件,然后对其进行压缩存储。如上传的文件存放的目录路径,压缩后的文件存放路径等等。

系统配置信息

我们将系统的配置信息都写入到配置文件中,让程序第一次运行时由单例配置类进行对配置文件的加载,获取配置信息。将系统配置信息写入到配置文件进行运行时加载可以增强系统的鲁棒性和灵活性。

{"hot_time" : 30,"server_port" : 8080,"server_ip" : "0.0.0.0","pack_suffix" : ".lz","pack_dir" : "./packdir/","back_dir" : "./backdir/","download_preffix" : "./download/","manager_file" : "./cloud.dat"
}
  • "hot_time" : 30:热更新时间为 30 秒。
  • "server_port" : 8080:服务器端口号为 8080。
  • "server_ip" : "0.0.0.0":服务器 IP 地址为 0.0.0.0,通常表示监听所有可用的网络接口。
  • "pack_suffix" : ".lz":打包文件的后缀为 .lz
  • "pack_dir" : "./packdir/":打包文件存储目录为当前目录下的 packdir 文件夹。
  • "back_dir" : "./backdir/":备份文件存储目录为当前目录下的 backdir 文件夹。
  • "download_preffix" : "./download/":下载文件的前缀为当前目录下的 download 文件夹。
  • "backup_file" : "./cloud.dat":备份文件信息的路径为当前目录下的 cloud.dat 文件。

这些配置项可以用于配置系统的各种参数,如服务器端口号、IP 地址、文件存储路径等。通过解析这些配置项,可以使系统在运行时根据预定义的参数进行设置和运行。在实际应用中,可以根据需要动态地读取和修改这些配置项,以满足不同环境下的需求。

单例配置类

单例配置类在系统的第一次运行时对配置文件进行加载,获取各配置信息。

#ifndef __MY_CON__
#define __MY_CON__#include <mutex>
#include "Util.hpp"#define CONFIG_FILE "./cloud.conf"namespace wzh
{class config{public:static config* getInstance(){if(_instance == NULL){_mutex.lock();if(_instance == NULL){_instance = new config();}_mutex.unlock();}return _instance;}int getHotTime(){return _hot_time;}int getServerPort(){return _server_port;}std::string getServerIp(){return _server_ip;}std::string getDownloadPreffix(){return _download_preffix;}std::string getPackFileSuffix(){return _pack_suffix;}std::string getPackDir(){return _pack_dir;}std::string getBackDir(){return _back_dir;}std::string getBackupFile(){return _backup_file;}private:config(){readConfigFile();}static config* _instance;static std::mutex _mutex;bool readConfigFile(){FileUtil fu(CONFIG_FILE);std::string body;if(fu.getContent(&body) == false){std::cout << "readConfigFile failed\n";return false;}Json::Value root;if(JsonUtil::deSerialize(body, &root) == false){std::cout << "parse config file failed\n";return false;}_hot_time = root["hot_time"].asInt();_server_ip = root["server_ip"].asString();_server_port = root["server_port"].asInt();_pack_dir = root["pack_dir"].asString();_back_dir = root["back_dir"].asString();_pack_suffix = root["pack_suffix"].asString();_download_preffix = root["download_preffix"].asString();_backup_file = root["backup_file"].asString();return true;}private:int _hot_time;int _server_port;std::string _server_ip;std::string _pack_suffix;std::string _pack_dir;std::string _back_dir;std::string _download_preffix;std::string _backup_file;};config* config::_instance = NULL;std::mutex config::_mutex;
}
#endif

代码中实现了单例模式类 config,用于读取系统配置信息并提供对外接口获取配置参数。单例模式实现使用了懒汉式单例模式,_instance_mutex 是静态成员变量,用于存储单例对象和保证线程安全。getInstance() 方法返回单例对象的指针,确保只有一个实例存在。其中readConfigFile() 方法用于从配置文件中读取配置信息。使用 FileUtil 类读取配置文件内容,然后通过 JSON 序列化工具类 JsonUtil 解析配置信息。还提供了一系列公有方法用于获取各种配置参数,如热更新时间、服务器端口、服务器 IP 等。

数据管理类

数据管理类对备份文件的信息进行有效组织,并提供一系列接口供其他模块功能来获取文件信息进行利用。

数据信息

typedef struct BackupInfo{bool pack_flag;size_t fsize;time_t atime;time_t mtime;std::string real_path;std::string pack_path; std::string url;void newBackupInfo(const std::string &realpath){config *con = config::getInstance();std::string packdir = con->getPackDir();std::string packsuffix = con->getPackFileSuffix();std::string download_preffix = con->getDownloadPreffix();FileUtil fu(realpath);pack_flag = false;fsize = fu.fileSize();mtime = fu.lastModTime();atime = fu.lastAccTime();real_path = realpath;pack_path = packdir + fu.fileName() + packsuffix;url = download_preffix + fu.fileName();}}BackupInfo;
  • bool pack_flag;:用于表示是否已经压缩的标志。

  • size_t fsize;:表示文件大小,存储文件的字节数。

  • time_t atime;:表示文件的最后访问时间。

  • time_t mtime;:表示文件的最后修改时间。

  • std::string real_path;:表示文件的实际路径,即文件在文件系统中的路径。

  • std::string pack_path;:表示文件的打包压缩路径,即文件在备份过程中打包压缩后存储的路径。

  • std::string url;:表示文件的下载 URL,即用户可以通过该 URL 下载文件。

  • void newBackupInfo(const std::string &realpath):这是一个成员函数,用于初始化备份信息。它接受一个 realpath 参数,即文件的实际路径。在该函数中,首先获取配置信息对象的实例,然后利用该实例获取备份相关的配置信息,包括打包目录、打包文件后缀和下载文件前缀。接着利用 FileUtil 类获取文件的大小、最后修改时间和最后访问时间,并设置其他字段的值。

数据管理

class DataManager{public:DataManager(){_backup_file = config::getInstance()->getBackupFile();pthread_rwlock_init(&_rwlock, NULL);InitLoad();}~DataManager(){pthread_rwlock_destroy(&_rwlock);}bool inSert(const BackupInfo &info){pthread_rwlock_wrlock(&_rwlock);_table[info.url] = info;pthread_rwlock_unlock(&_rwlock);storage();return true;}bool upDate(const BackupInfo &info){pthread_rwlock_wrlock(&_rwlock);_table[info.url] = info;pthread_rwlock_unlock(&_rwlock);storage();return true;}bool getOneByURL(const std::string &url, BackupInfo *info){pthread_rwlock_wrlock(&_rwlock);auto it = _table.find(url);if(it == _table.end()){pthread_rwlock_unlock(&_rwlock);return false;}*info = it->second;pthread_rwlock_unlock(&_rwlock);return true;}bool getOneByRealPath(const std::string &realpath, BackupInfo *info){pthread_rwlock_wrlock(&_rwlock);auto it = _table.begin();for(; it != _table.end(); it++){if(it->second.real_path == realpath){*info = it->second;pthread_rwlock_unlock(&_rwlock);return true;}}pthread_rwlock_unlock(&_rwlock);return false;}bool getAll(std::vector<BackupInfo> *arry){pthread_rwlock_wrlock(&_rwlock);auto it = _table.begin();for(; it != _table.end(); it++){arry->push_back(it->second);}pthread_rwlock_unlock(&_rwlock);return true;}bool storage(){std::vector<BackupInfo> arry;getAll(&arry);Json::Value root;for(int i = 0; i < arry.size(); i++){Json::Value item;item["pack_flag"] = arry[i].pack_flag;item["fsize"] = (Json::Int64)arry[i].fsize;item["atime"] = (Json::Int64)arry[i].atime;item["mtime"] = (Json::Int64)arry[i].mtime;item["pack_path"] = arry[i].pack_path;item["real_path"] = arry[i].real_path;item["url"] = arry[i].url;root.append(item);}std::string body;JsonUtil::serialize(root, &body);FileUtil fu(_backup_file);fu.setContent(body);return true;}bool InitLoad(){FileUtil fu(_backup_file);if(fu.exits() == false) return true;std::string body;fu.getContent(&body);Json::Value root;JsonUtil::deSerialize(body, &root);for(int i = 0; i < root.size(); i++){BackupInfo info;info.pack_flag = root[i]["pack_flag"].asBool();info.fsize = root[i]["fsize"].asInt();info.atime = root[i]["atime"].asInt64();info.mtime = root[i]["mtime"].asInt64();info.pack_path = root[i]["pack_path"].asString();info.real_path = root[i]["real_path"].asString();info.url = root[i]["url"].asString();inSert(info);}return true;}private:std::string _backup_file;pthread_rwlock_t _rwlock;std::unordered_map<std::string, BackupInfo> _table;};

DataManager 数据管理类,主要用于管理备份信息的存储、更新和获取操作,并实现了从文件中加载和持久化备份信息的功能。

函数功能
DataManager()初始化了备份文件路径、读写锁,并调用 InitLoad() 函数加载备份信息到内存中。
~DataManager()释放了读写锁。
inSert(const BackupInfo &info)向数据表中插入备份信息。
upDate(const BackupInfo &info)更新数据表中的备份信息。
getOneByURL(const std::string &url, BackupInfo *info)根据 URL 获取单个备份信息。
getOneByRealPath(const std::string &realpath, BackupInfo *info)根据实际路径获取单个备份信息。
getAll(std::vector<BackupInfo> *arry)获取所有备份信息。
storage()将备份信息持久化存储到文件中。
InitLoad()从备份文件中加载备份信息到内存中。

成员变量:

  • _backup_file:备份文件的路径。
  • _rwlock:读写锁,用于保护数据表的并发访问。
  • _table:使用无序映射存储备份信息,键为备份信息的 URL。

这个 DataManager 类的设计使得它能够方便地进行备份信息的存储、更新和获取,并且在对象构造时自动加载已有的备份信息,提高了代码的易用性和可维护性。

热点管理类

热点管理类用于遍历备份文件夹中的文件,对其中的文件进行判断是否为热点文件,对非热点文件进行压缩处理。

class HotManager{public:HotManager(){config *con = config::getInstance();_back_dir = con->getBackDir();_pack_dir = con->getPackDir();_pack_suffix = con->getPackFileSuffix();_hot_time = con->getHotTime();FileUtil tmp1(_back_dir);FileUtil tmp2(_pack_dir);tmp1.createDirectory();tmp2.createDirectory();}bool RunModule(){while(1){FileUtil fu(_back_dir);std::vector<std::string> arry;fu.scanDirectory(&arry);for(auto &a : arry){if(HotJudge(a) == true) continue;BackupInfo bi;if(_data->getOneByRealPath(a, &bi) == false){bi.newBackupInfo(a);}FileUtil tmp(a);tmp.comPress(bi.pack_path);tmp.reMove();bi.pack_flag = true;_data->upDate(bi);}usleep(1000);}return true;}private:bool HotJudge(const std::string &filename){FileUtil fu(filename);time_t last_atime = fu.lastAccTime();time_t cur_time = time(NULL);if(cur_time - last_atime <= _hot_time) return true;return false;}private:std::string _back_dir;std::string _pack_dir;int _hot_time;std::string _pack_suffix;    };
函数说明
HotManager()在对象创建时从配置文件中获取备份目录、打包目录、热文件时间阈值等参数,并且创建备份目录和打包目录。
RunModule()运行热备份模块的主循环。该函数通过不断扫描备份目录中的文件,判断是否为热文件。若文件不是热文件,则进行压缩备份操作,并更新备份信息。该循环会持续执行,通过 usleep(1000) 控制循环的执行频率。
HotJudge(const std::string &filename)判断文件是否为热文件。通过获取文件的最后访问时间,与当前时间进行比较判断文件是否为热文件。

成员变量:

  • _back_dir:备份目录的路径。
  • _pack_dir:打包目录的路径。
  • _hot_time:热文件时间阈值,用于判断文件是否为热文件。
  • _pack_suffix:打包文件的后缀名。

这个 HotManager 类使得它能够周期性地监视备份目录中的文件,并根据热文件的判断条件执行相应的操作,从而实现了备份管理的自动化。

业务处理类

服务端提供提供了上传文件、列出文件列表和下载文件的功能,对客户端发送来的请求做出相应的处理后返回处理结果,并且下载支持断点续传功能。

class server{public:server(){config *con = config::getInstance();_server_port = con->getServerPort();_server_ip = con->getServerIp();_download_preffix = con->getDownloadPreffix();}bool RunModule(){_server.Post("/upload", UpLoad);_server.Get("/listshow", ListShow);_server.Get("/", ListShow);std::string download_url = _download_preffix + "(.*)";_server.Get(download_url, DownLoad); // (.*)匹配任意字符任意次_server.listen(_server_ip.c_str(), _server_port);return true;}private:static void UpLoad(const httplib::Request &req, httplib::Response &res){auto ret = req.has_file("file");if(ret == false){res.status = 400;return;}const auto &file = req.get_file_value("file");std::string back_dir = config::getInstance()->getBackDir();std::string realpath = back_dir + FileUtil(file.filename).fileName();if(FileUtil(back_dir).exits() == false) FileUtil(back_dir).createDirectory();FileUtil fu(realpath);fu.setContent(file.content);BackupInfo info;info.newBackupInfo(realpath);_data->inSert(info);}static std::string timetoStr(time_t t){std::string tmp = std::ctime(&t);return tmp;}static void ListShow(const httplib::Request &req, httplib::Response &res){std::vector<BackupInfo> arry;_data->getAll(&arry);std::stringstream ss;ss << "<html><head><title>Download</title></head>";ss << "<body><h1>Download</h1><table>";for(auto &a :arry){ss << "<tr>";std::string filename = FileUtil(a.real_path).fileName();ss << "<td><a href='" << a.url << "'>" << filename << "</a></td>";ss << "<td align='right'>" << timetoStr(a.mtime) << "</td>";ss << "<td align='right'>" << a.fsize / 1024 << "k</td>";ss << "</tr>";}ss << "</table></body></html>";res.body = ss.str();res.set_header("Content-Type", "text/html");res.status = 200;}static std::string getETag(const BackupInfo &info){FileUtil fu(info.real_path);std::string etag = fu.fileName();etag += "-";etag += std::to_string(info.fsize);etag += "-";etag += std::to_string(info.mtime);return etag;}static void DownLoad(const httplib::Request &req, httplib::Response &res){BackupInfo info;_data->getOneByURL(req.path, &info);if(info.pack_flag == true){FileUtil fu(info.pack_path);fu.unCompress(info.real_path);fu.reMove();info.pack_flag = false;_data->upDate(info);}bool retrans = false;std::string old_etag;if(req.has_header("If-Range")){old_etag = req.get_header_value("If-Range");if(old_etag == getETag(info)) retrans = true;}FileUtil fu(info.real_path);  fu.getContent(&res.body);res.set_header("Accept-Ranges", "bytes");res.set_header("ETag", getETag(info));res.set_header("Content-Type", "application/octet-stream");if(retrans == false) res.status = 200;else res.status = 206;}private:int _server_port;std::string _server_ip;std::string _download_preffix;httplib::Server _server;};
  1. DownLoad 函数中检查了 If-Range 头部,以确定是否需要重新传输文件。这是实现断点续传的一种方式。如果客户端在请求中提供了上次请求时服务器返回的 ETag(文件标识),并且与当前文件的 ETag 相匹配,那么可以断定客户端已经具有相应的部分文件。在这种情况下,你可以返回状态码 206 Partial Content,表示只返回文件的一部分内容。否则,返回状态码 200 OK,表示返回整个文件内容。

  2. DownLoad 函数中将文件内容设置到响应体中,并设置了 Accept-RangesETag 头部。Accept-Ranges: bytes 表示服务器支持字节范围请求,这是断点续传所需的。ETag 是文件的标识,用于判断文件是否发生了变化。

  3. UpLoad 函数中处理了文件上传的逻辑。首先,检查请求中是否包含文件,然后获取文件内容并保存到指定目录中。接着,创建一个 BackupInfo 对象并插入到数据管理模块中。

客户端

客户端的开发在Windows上,使用的工具为vs2017及以上版本。

注意:需要支持C++17.

数据管理类

数据管理类对文件进行描述组织,并提供增加修改浏览等功能,能够进行持久化存储。

class dataManager{public:dataManager(const std::string& backupfile):_backup_file(backupfile){InitLoad();}void splitStr(const std::string& str, const std::string& sep, std::vector<std::string>* arry){size_t cur = 0, pos = 0;while (cur < str.size()){pos = str.find(sep, cur);if (pos == std::string::npos) break;std::string tmp = str.substr(cur, pos - cur);arry->push_back(tmp);cur = pos + sep.size();}if (cur < str.size()){std::string tmp = str.substr(cur);arry->push_back(tmp);}}bool InitLoad(){FileUtil fu(_backup_file);std::string body;fu.getContent(&body);std::vector<std::string> arry;splitStr(body, "\n", &arry);for (auto& str : arry){std::vector<std::string> tmp;splitStr(str, " : ", &tmp);if (tmp.size() != 2) continue;_table[tmp[0]] = tmp[1];}return true;}bool inSert(const std::string& key, const std::string& val){_table[key] = val;storage();return true;}bool upData(const std::string& key, const std::string& val){_table[key] = val;storage();return true;}bool getOneByKey(const std::string& key, std::string* val){auto it = _table.find(key);if (it == _table.end()) return false;*val = it->second;return true;}protected:bool storage(){std::stringstream ss;for (auto it = _table.begin(); it != _table.end(); it++){ss << it->first << " : " << it->second << "\n";}FileUtil fu(_backup_file);fu.setContent(ss.str());return true;}private:std::string _backup_file;std::unordered_map<std::string, std::string> _table;};
  • dataManager 类用于管理键值对数据,其中键和值都是字符串类型。
  • 构造函数 dataManager(const std::string& backupfile) 接受一个参数 backupfile,表示备份文件的路径,用于存储数据。
  • storage() 方法将当前内存中的数据持久化到备份文件中。它将所有键值对拼接成一个字符串,并将其写入备份文件。
  • splitStr() 方法用于将字符串按照指定的分隔符拆分成字符串数组。
  • InitLoad() 方法用于从备份文件中加载数据到内存中。它读取备份文件的内容,按行分割,然后将键值对存储到内存中的哈希表中。
  • inSert() 方法用于向数据管理类中插入新的键值对,并将数据持久化到备份文件中。
  • upData() 方法用于更新指定键对应的值,并将更新后的数据持久化到备份文件中。
  • getOneByKey() 方法用于根据给定的键获取对应的值,并将值通过参数返回。

文件备份类

文件备份类实现的功能是定期扫描指定目录下的文件,并将需要上传的文件发送到服务器。

class Backup{public:Backup(const std::string &backdir, const std::string &backfile):_back_dir(backdir){_data = new dataManager(backfile);FileUtil(_back_dir).createDirectory();}std::string getFileIdentifier(const std::string& filename){FileUtil fu(filename);std::stringstream ss;ss << fu.fileName() << "-" << fu.fileSize() << "-" << fu.lastModTime();return ss.str();}bool runModue(){while (1){FileUtil fu(_back_dir);std::vector<std::string> arry;fu.scanDirectory(&arry);for (auto& a : arry){if (isNeedUpload(a)){if (upLoad(a)){_data->inSert(a, getFileIdentifier(a));std::cout << "upload successful!\n";}}}Sleep(1);}return true;}protected:bool isNeedUpload(const std::string& filename){std::string id;if (_data->getOneByKey(filename, &id)){std::string new_id = getFileIdentifier(filename);if (new_id == id) return false;}FileUtil fu(filename);if (time(NULL) - fu.lastModTime() < 5) return false;std::cout << filename << " need upload!\n";return true;}bool upLoad(const std::string& filename){FileUtil fu(filename);std::string body;fu.getContent(&body);httplib::Client cli(SERVER_IP, SERVER_PORT);httplib::MultipartFormData item;item.content = body;item.filename = fu.fileName();item.name = "file";item.content_type = "application/octet-stream";httplib::MultipartFormDataItems items;items.push_back(item);auto res = cli.Post("/upload", items);if (!res || res->status != 200){return false;}return true;}private:std::string _back_dir;dataManager* _data;};
  • 构造函数 Backup 接收备份目录和备份文件名作为参数,初始化备份客户端对象。
  • getFileIdentifier 函数用于生成文件标识符,它基于文件名、文件大小和最后修改时间生成一个字符串。
  • runModule 函数是备份客户端的主要执行逻辑。它循环扫描备份目录下的文件,如果发现需要上传的文件,则调用 upLoad 函数上传文件,并将文件信息记录到备份数据中。
  • isNeedUpload 函数用于检查文件是否需要上传。它检查备份数据中是否已存在相同文件,并比较文件最后修改时间,以决定是否需要上传文件。
  • upLoad 函数负责上传文件到服务器。它使用 httplib 库创建一个客户端,将文件内容以 multipart/form-data 格式发送到指定的上传端点 /upload。如果上传成功(返回状态码 200),则返回 true,否则返回 false。

总结

开发云备份项目涉及多个技术领域,包括文件操作、网络通信、并发处理等。需要解决各种技术挑战,例如如何有效地上传和下载大文件、处理网络请求超时、处理并发上传等。

而且在开发云备份项目可能涉及学习新的库、框架或技术。这可能包括学习使用第三方库来处理HTTP请求、学习文件压缩和解压缩技术、学习关于网络安全的最佳实践等。开发云备份项目也是一个持续学习和改进的过程,会不断遇到新的挑战和问题,需要持续学习并改进解决方案。

总的来说,开发云备份项目是一个充满挑战和机会的过程,通过这个项目可以提高技术能力、团队协作能力和项目管理能力。同时,也会从中获得成就感和满足感,因为付出的努力将会变成一个实实在在的产品或服务。

最后,云备份专栏持续更新中,对项目周边知识点以及项目难点进行清扫,以及项目的更新迭代,欢迎佬们提出自己的问题观点想法,加入对代码的更新迭代队伍中。

在这里插入图片描述

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

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

相关文章

软件测试【三】Python中的数据类型

一、Python中的数据类型: python中的list列表定义 在Python中&#xff0c;list是一种有序的数据类型&#xff0c;可以存储任意类型的对象&#xff0c;包括数字、字符串、布尔值、函数等。 定义一个list列表可以使用中括号[]来表示&#xff0c;其中每个元素之间用逗号隔开。以…

anomalib1.0学习纪实

回顾&#xff1a;细分、纵深、高端、上游、积累、极致。 回顾&#xff1a;产品化&#xff0c;资本化&#xff0c;规模化&#xff0c;大干快上&#xff0c;小农思维必死无疑。 春节在深圳新地中央&#xff0c;学习anomalib1.0。 一、安装&#xff1a; 1、常规安装 采用的是…

蓝桥杯:C++排列与组合

排列是暴力枚举时的常见操作。有以下两种情况。 C的 next_permutation()是全排列函数&#xff0c;只能输出序列中所有元素的全排列。 本节将给出手写排列和组合的代码。因为在很多场合中不能使用系统自带的排列函数&#xff0c;所以需要自己编写。 全排列函数&#xff1a;nex…

SpringCloud之Eureka注册中心和负载均衡

SpringCloud之Eureka注册中心和负载均衡 微服务技术栈认识微服务单体架构分布式架构微服务 微服务拆分及远程调用微服务拆分注意事项 Eureka注册中心提供者与消费者原理分析服务调用出现的问题Eureka的作用 使用流程1、搭建EurekaServer2、注册user-service3、在order-service完…

BUGKU-WEB 你必须让他停下

题目描述 题目截图如下&#xff1a; 进入场景看看&#xff1a; 解题思路 图片会消失,那应该是使用了js来控制根据提示,那就是要停止js才会看到flag (也就是要抓包,不要陷入停止js的思维) 相关工具 F12大法Burp Suit抓包工具 解题步骤 出现图片的时候,源码中确实出现…

NULL和nullptr到底是什么?它们的区别又是什么?

目录 1.前言 2.剖析NULL 3. 剖析nullptr 4.nullptr与NULL的区别 1.前言 为了保持良好的编程习惯&#xff0c;我们在声明某个变量时都应该给这个变量合适的初始值&#xff0c;不然容易出现不可预知的错误。对于指针来说更是如此&#xff0c;在c语言中我们就通常将暂时没有指向…

【Spring MVC篇】返回响应

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【Spring MVC】 本专栏旨在分享学习Spring MVC的一点学习心得&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目录 一、返回静态页面…

(13)Hive调优——动态分区导致的小文件问题

前言 动态分区指的是&#xff1a;分区的字段值是基于查询结果自动推断出来的&#xff0c;核心语法就是insertselect。 具体内容指路文章&#xff1a; https://blog.csdn.net/SHWAITME/article/details/136111924?spm1001.2014.3001.5501文章浏览阅读483次&#xff0c;点赞15次…

如何买卖基金

一、从哪买卖&#xff1f; &#xff08;一&#xff09;购买渠道 有两种购买渠道&#xff1a;直销平台和代销平台。 1.直销平台 就是基金公司。 每个基金公司只能卖自家基金产品。比如招商基金只能卖招商基金管理的基金&#xff0c;而不能卖广发基金的产品。 如何去基金公司购…

2024年最新onlyfans虚拟信用卡订阅教程

一、Onlyfans是什么&#xff1f; OnlyFans是一个允许创作者分享自己的独家内容的平台&#xff0c;简称o站。这个平台允许创作者创建一个订阅服务&#xff0c;粉丝需要支付费用才能访问其独家内容。 本文将教你如何使用虚拟卡在OnlyFans上进行充值。 二、如何使用虚拟卡支付 O…

变形金刚:第 2 部分:变形金刚的架构

目录 一、说明 二、实现Transformer的过程 第 1 步&#xff1a;代币化&#xff08;Tokenization&#xff09; 第 2 步&#xff1a;对每个单词进行标记嵌入 第 3 步&#xff1a;对每个单词进行位置嵌入 第 4 步&#xff1a;输入嵌入 第 5 步&#xff1a;编码器层 2.5.1 多头自注…

【MySQL】高度为2和3时B+树能够存储的记录数量的计算过程

文章目录 题目答案高度为2时的B树高度为3时的B树总结 GPT4 对话过程 题目 InnoDB主键索引的Btree在高度分别为 2 和 3 时&#xff0c;可以存储多少条记录&#xff1f; 答案 高度为2时的B树 计算过程&#xff1a; 使用公式 ( n 8 ( n 1 ) 6 16 1024 ) (n \times 8 …

二维数组及函数的非函数实现

2024年2月14日 1.请编程实现二维数组的杨慧三角 #include<stdio.h> #include<stdlib.h> #include<string.h> void Yanghui(int n,int (*p)[n]) {for(int i0;i<n;i){for(int j0;j<i;j){if(j0||ij){*(*(pi)j)1;}else{*(*(pi)j)*(*(pi-1)j-1)*(*(pi-1)j)…

相机图像质量研究(15)常见问题总结:光学结构对成像的影响--暗角

系列文章目录 相机图像质量研究(1)Camera成像流程介绍 相机图像质量研究(2)ISP专用平台调优介绍 相机图像质量研究(3)图像质量测试介绍 相机图像质量研究(4)常见问题总结&#xff1a;光学结构对成像的影响--焦距 相机图像质量研究(5)常见问题总结&#xff1a;光学结构对成…

456. 车站分级(拓扑排序,虚拟点建图)

活动 - AcWing 一条单向的铁路线上&#xff0c;依次有编号为 1, 2, …, n1,  的 n 个火车站。 每个火车站都有一个级别&#xff0c;最低为 1 级。 现有若干趟车次在这条线路上行驶&#xff0c;每一趟都满足如下要求&#xff1a;如果这趟车次停靠了火车站 x&#xff0c;…

Elasticsearch:特定领域的生成式 AI - 预训练、微调和 RAG

作者&#xff1a;来自 Elastic Steve Dodson 有多种策略可以将特定领域的知识添加到大型语言模型 (LLM) 中&#xff0c;并且作为积极研究领域的一部分&#xff0c;正在研究更多方法。 对特定领域数据集进行预训练和微调等方法使 LLMs 能够推理并生成特定领域语言。 然而&#…

【深入理解DETR】DETR的原理与算法实现

1 DETR算法概述 ①端到端 ②Transformer-model 之前的方法都需要进行NMS操作去掉冗余的bounding box或者手工设计anchor&#xff0c; 这就需要了解先验知识&#xff0c;增加从超参数anchor的数量&#xff0c; 1.1 训练测试框架 一次从图像中预测n个object的类别 训练阶段我们…

Attention Is All Your Need论文翻译

0.摘要 这个统治序列转换模型是基于复杂循环或者卷积神经网络&#xff0c;它包含编码器和解码器。表现最好的模型也通过注意力机制来连接编码器和解码器。我们提出了一个新的简单网络架构——Transformer,它仅仅是是基于注意力机制&#xff0c;完全免去递推和卷积。在两个机器…

C语言——函数(第五讲)(上)

C语言——函数&#xff08;第五讲&#xff09; 前言函数的概念库函数标准库和库函数库函数的使用方法 ⾃定义函数形式形参和实参形参和实参的关系 前言 Hello,各位C语言的小伙伴们&#xff0c;大家过年好&#xff0c;我是莹莹。停更差不多一个月了&#xff0c;都是懒惰作怪&am…