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

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

文章目录

  • 前言
  • 工具类实现
    • 文件实用工具类
      • 代码实现
    • 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.rhkb.cn/news/257587.html

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

相关文章

高级FPGA开发之基础协议PCIe(二)

高级FPGA开发之基础协议之PCIe&#xff08;二&#xff09; 一、TLP报文类型 在PCIe总线中&#xff0c;存储器读写、I/O读写和配置读写请求TLP主要由以下几类报文组成&#xff1a; 1.1 存储器读请求TLP和读完成TLP 当PCIe主设备&#xff08;RC或者EP&#xff09;访问目标设备…

DS:二叉树的链式结构及实现

创作不易&#xff0c;友友们给个三连吧&#xff01;&#xff01; 一、前言 前期我们解释过二叉树的顺序结构&#xff08;堆&#xff09;为什么比较适用于完全二叉树&#xff0c;因为如果用数组来实现非完全二叉树&#xff0c;那么数组的中间部分就可能会存在大量的空间浪费。 …

【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 …

STM32F1 引脚重映射功能

STM32 端口引脚重映射 文章目录 STM32 端口引脚重映射前言1、查阅芯片数据手册1.1 串口引脚重映射描述 2、代码部分2.1 核心代码部分 3、实验现象4、总结 前言 在写程序时遇到想要的端口功能&#xff0c;而这个引脚又被其它的功能占用了无法删除掉或直接使用&#xff0c;这种情…

实例分割论文阅读之:《Mask Transfiner for High-Quality Instance Segmentation》

1.摘要 两阶段和基于查询的实例分割方法取得了显著的效果。然而&#xff0c;它们的分段掩模仍然非常粗糙。在本文中&#xff0c;我们提出了一种高质量和高效的实例分割Mask Transfiner。我们的Mask Transfiner不是在规则的密集张量上操作&#xff0c;而是将图像区域分解并表示…

深入探索Pandas读写XML文件的完整指南与实战read_xml、to_xml【第79篇—读写XML文件】

深入探索Pandas读写XML文件的完整指南与实战read_xml、to_xml XML&#xff08;eXtensible Markup Language&#xff09;是一种常见的数据交换格式&#xff0c;广泛应用于各种应用程序和领域。在数据处理中&#xff0c;Pandas是一个强大的工具&#xff0c;它提供了read_xml和to…

蓝桥杯嵌入式学习记录——按键的使用

目录 一、按键原理简介 二、cubeMX的配置 三、按键的短按代码 四、按键的长按代码 一、按键原理简介 在STM32中&#xff0c;按键连接通常使用GPIO&#xff08;通用输入/输出&#xff09;端口来实现。当按键未被按下时&#xff0c;GPIO端口处于高电平状态&#xff08;即1&am…

Linux第48步_编译正点原子的出厂Linux内核源码

编译正点原子的出厂 Linux 内核源码&#xff0c;为后面移植linux做准备。研究对象如下&#xff1a; 1)、linux内核镜像文件“uImage” 路径为“arch/arm/boot”&#xff1b; 2)、设备树文件“stm32mp157d-atk.dtb” 路径为“arch/arm/boot/dts” 3)、默认配置文件“stm32m…

3D高斯溅射:面向三维场景的实时渲染技术

1. 前言 高斯溅射技术【1】一经推出&#xff0c;立刻引起学术界和工业界的广泛关注。相比传统的隐式神经散射场渲染技术&#xff0c;高斯溅射依托椭球空间&#xff0c;显性地表示多目图像的三维空间关系&#xff0c;其计算效率和综合性能均有较大的提升&#xff0c;且更容易理…

【AI视野·今日NLP 自然语言处理论文速览 第七十八期】Wed, 17 Jan 2024

AI视野今日CS.NLP 自然语言处理论文速览 Wed, 17 Jan 2024 (showing first 100 of 163 entries) Totally 100 papers &#x1f449;上期速览✈更多精彩请移步主页 Daily Computation and Language Papers Deductive Closure Training of Language Models for Coherence, Accur…

VueCLI核心知识3:全局事件总线、消息订阅与发布

这两种方式都可以实现任意两个组件之间的通信 1 全局事件总线 1.安装全局事件总线 import Vue from vue import App from ./App.vueVue.config.productionTip false/* 1.第一种写法 */ // const Demo Vue.extend({}) // const d new Demo()// Vue.prototype.x d // 把Dem…

XML学习

XML学习 1 XML介绍2 XML语法3 XML特殊字符4 XML文档结构5 XML命名空间 1 XML介绍 XML是可扩展的标记性语言&#xff0c;常用来传输和存储数据。可见于Web中的web.xml和Maven中的pom.xml version是版本&#xff0c;encoding是编码 <?xml version"1.0" encodin…

IMX6ULL移植U-Boot 2022.04

目录 目录 1.编译环境以及uboot版本 2.默认编译测试 3.uboot中新增自己的开发板 3.编译测试 4.烧录测试 5.patch文件 1.编译环境以及uboot版本 宿主机Debian12u-boot版本lf_v2022.04 ; git 连接GitHub - nxp-imx/uboot-imx: i.MX U-Boot交叉编译工具gcc-arm-10.3-2021.0…

【PyQt】08 - 编辑Tab顺序

文章目录 前言一、Tab顺序二、编辑Tab顺序总结 前言 介绍了什么是Tab顺序&#xff0c;以及如何修改Tab顺序。 一、Tab顺序 当你的界面设计好之后&#xff0c;在输入栏按住Tab按键&#xff0c;他会按照你摆放的顺序一次转跳 二、编辑Tab顺序 方法一 然后鼠标左击就可以改变…

PHP毕业设计图片分享网站76t17

图片分享网站主要是为了提高工作人员的工作效率和更方便快捷的满足用户&#xff0c;更好存储所有数据信息及快速方便的检索功能&#xff0c;对系统的各个模块是通过许多今天的发达系统做出合理的分析来确定考虑用户的可操作性&#xff0c;遵循开发的系统优化的原则&#xff0c;…

Vegeta压测工具学习与使用

Vegeta压测工具学习与使用 目标&#xff1a; 能够在命令行下使用Vegeta对指定API进行测试了解如何导出结果&#xff0c;以及能获得什么样的结果(P99,P99.9,QPS)探索能否导出其他结果&#xff0c;是否能够执行复杂命令或简易脚本等 时间比较紧迫&#xff0c;预计两到三个小时内完…

Sentinel 流控-链路模式

链路模式 A B C 三个服务 A 调用 C B 调用 C C 设置流控 ->链路模式 -> 入口资源是 A A、B 服务 package com.learning.springcloud.order.controller;import com.learning.springcloud.order.service.BaseService; import org.springframework.beans.factory.annotatio…

【JAVA】计算机软件工程人工智能研究生复试资料整理

1、JAVA 2、计算机网络 3、计算机体系结构 4、数据库 5、计算机租场原理 6、软件工程 7、大数据 8、英文 自我介绍 1. Java 1. == 和 equals的区别 比较基本数据类型是比较的值,引用数据类型是比较两个是不是同一个对象,也就是引用是否指向同 一个对象,地址是否相同,equ…

鸿蒙开发系列教程(十八)--页面内动画(1)

页面内的动画 显示动画 语法&#xff1a;animateTo(value: AnimateParam, event: () > void): void 第一个参数指定动画参数 第二个参数为动画的闭包函数。 如&#xff1a;animateTo({ duration: 1000, curve: Curve.EaseInOut }, () > {动画代码}&#xff09; dura…

手撕链表OJ

&#x1d649;&#x1d65e;&#x1d658;&#x1d65a;!!&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦&#x1f44f;&#x1f3fb;‧✧̣̥̇‧✦ &#x1f44f;&#x1f3fb;‧✧̣̥̇:Solitary-walk ⸝⋆ ━━━┓ - 个性标签 - &#xff1a;来于“云”的“羽球人”。…