【云备份】客户端实现 及 项目整体总结

文章目录

  • 客户端
    • 客户端实现思想
    • 客户端文件操作类的设计与拷贝
      • Util.hpp的设计
      • data.hpp的设计
        • Storage —— 持久化存储
        • Initload——数据初始化加载
      • cloud.hpp的设计
        • GetFileIdentifier——创建文件唯一标识
        • Upload—— 文件上传
        • IsNeedupload —— 客户端文件是否需要上传判断
        • RunModule —— 运行模块
  • 项目总结
  • 整体代码
    • 服务器端(Linux下实现)
      • util.hpp(实用文件工具模块)
      • service.hpp(业务处理模块)
      • makefile
      • hot.hpp(热点管理模块)
      • data.hpp (数据管理模块)
      • config.hpp(配置加载文件模块)
      • cloud.conf
    • 客户端(VS2022下实现)
      • util.hpp(实用文件工具模块)
      • data.hpp (数据管理模块)
      • cloud.hpp (文件备份模块)
      • cloud.cpp

客户端

客户端实现思想

客户端实现功能:
自动对指定文件夹的文件进行备份


数据管理模块的实现思想:
内存存储 :高访问效率 使用hash表_table
持久化存储:文件存储
文件存储涉及到数据的序列化 而在VS中安装jsoncpp麻烦 直接自定义序列化格式 key value
key表示文件路径名 value表示 文件唯一标识
文件的唯一标识:用于判断上次文件上传后有没有被修改过


客户端文件操作类的设计与拷贝

以下操作都在VS中进行

Util.hpp的设计

客户端文件操作类 与 服务端的文件实用工具类 基本没有差别
所以直接复制 util.hpp
把 FileUtil类中 的 compress 压缩函数 与 uncompress 解压缩 函数 以及 json类(序列化与反序列化) 删除

data.hpp的设计

创建 data.hpp
在cloud命名空间中 设计 DataManger 类 用于设计客户端的数据管理模块


_backup_file 用于备份信息的持久化存储文件
_table 是一个 key值为string value值为string 的哈希表 用于存储数据


Storage —— 持久化存储

遍历_table哈希表,将表中的key和value值都写入到ss字符串流中

使用 _backup_file 备份信息持久化存储文件 实例化一个 FileUtil类的 对象 fu
调用 FileUtil类的 SetContent 函数 将 ss字符串流中的数据 写入到 fu中


Initload——数据初始化加载

想要实现 InitLoad函数 就需要先实现分割功能

按照换行进行分割 得到一行一行的数据
在每一行中 按照空格进行分割 即可得到 文件名唯一标识


所以创建Split函数 实现分割功能

定义查找到分隔符的位置pos 以及 起始偏移量 idx 并初始化为0


find函数 的第一个参数为查找的字符 第二个参数为 偏移量
在while循环中 依次寻找对应的分隔符位置pos 以idx起始偏移量起始寻找


说明数据之前存在两个以上的分隔符 ,则偏移量为当前pos分隔符位置再加上分隔符个数
并重新查找分隔符位置pos


substr函数的第一个参数为 截取起始位置 第二个参数为长度


例如: abc qwe sdf
借助 substr 函数 从idx偏移量处 进行截取 pos位置为当前空格位置 pos-idx 即 截取的数据长度
再将tmp添加到 arry数组中
count 数量加1 表示 数据增添一个


若为最后一个数据 则查找不到空格直接跳出循环 还需将最后一个数据放入数组arry中



通过 _backup_file (备份信息的持久化存储文件) 实例化一个 FileUtil类的对象 fu
将_backup_file中的数据 读取到body字符串中


再将body中的数据通过 Split 函数 进行分割成一行一行的数据 并添加到arry数组中
此时arry数组中的元素 即为一行数据


想要把一行数据分割成文件名和唯一标识
就还需要借助 Split 函数 将一行数据通过空格分割开
最终在哈希表中使文件名和唯一标识 一 一对应



cloud.hpp的设计

设计Backup类 设置私有成员变量
_back_dir 要监控的文件夹
_data 数据管理类


GetFileIdentifier——创建文件唯一标识

通过filename 实例化一个对象fu
创建一个 字符串流 ss
通过调用 FileUtil类的函数 将文件名 - 文件大小 - 最后一次修改时间 写入到ss中
最后返回ss的字符串格式


Upload—— 文件上传

用 宏定义 IP地址 192.144.206.100(云服务器的公网IP) 为 SERVER_ADDR
用 宏定义 port端口号 9090 为 SERVER_PORT


通过filename 实例化一个 FileUtil类的对象 fu
再通过FileUtil类的 GetContent 函数 将 filename中的数据 传入 body字符串中


由于需要httplib,h 头文件 所以要把linux下的 该头文件 先传到桌面上 再导入 VS中

将属于linux下的 httplib.h 头文件 发送到桌面上


将VS的cloud_client 客户端 的路径复制下来 并打开


将httplib.h头文件托送到当前路径下


打开ckoud_client 客户端 添加 头文件 httplib.h


通过宏定义的 IP地址和 端口号 实例化一个 Client类的对象 client


MultipartFormDataMap 内部包含四个字段
name为字段名称
content为文件内容
filename为文件名称
coneent_type 为正文类型


创建 MultipartFormDataMap 类型的对象 item
文件内容为 body字符串中的数据
文件名称为 FileName函数 返回 的文件名
辨别字段为 file(与服务器端的serice 中的 upload函数 相同)
application / octet - stream 表示二进制流数据


调用httplib库中的Client类的 Post请求
因为要与服务器端的资源路径相统一 所以使用 /upload 进行 Post请求
以及items(将文件内容 文件名称 数据类型等上传)


IsNeedupload —— 客户端文件是否需要上传判断

若 文件是新增的 则需判断是否为历史备份信息

调用Datamanger 类的 GetOneBykey 函数 判断是否为唯一标识
若返回false 则说明找到了对应的历史备份信息 并将标识信息存储到id中

调用cloud类的 GetFileIdentifier 函数 获取新的标识信息 new_id
若 id 与 new_id 相等 则说明与历史文件信息一致 就不需要再次上传了


若id与new_id不相等 则说明 备份信息被修改了 需要重新上传

但在其中需要考虑一种特殊情况
有可能一个文件比较大 所以正在一点一点拷贝到这个目录下
拷贝需要一个过程 如果每次遍历 都会判断标识不一致 需要上传
就可能上传 上百次 这样就非常不合理
因此应该判断一个文件 在一段时间都没有被修改过 才能上传

time(NULL) 表示当前系统时间
LastMtime 函数表示 最后一次修改时间
若3秒内被修改过 就认为文件还在修改中 就不需要上传


RunModule —— 运行模块

通过 _back_dir 要监控的文件夹 实例化一个 FileUtil类的对象 fu
再通过 FileUtil类的 ScanDirectory 函数 将_back_dir的数据 传入 arry数组中


遍历arry数组 将数组中的每一个元素 通过 IsNeedUpload 函数 进行判断
看是否需要上传 若不需要上传 则重新遍历到下一个数组元素

通过Upload 函数 进行上传
若上传成功 则将文件名称 和唯一标识 通过insert 进行添加 构成新的备份文件信息


项目总结

项目名称:云备份系统
项目功能:搭建云备份服务器与客户端,客户端程序运行在客户机上自动将指定目录下的文件备份到服务器,并且能够支持浏览器查看与下载,其中下载支持断点续传功能,并且服务器端对备份的文件进行热点管理,将长时间无访问
文件进行压缩存储。
开发环境: centos7.6/vim、g++、gdb、makefile 以及 windows10/vs2022
技术特点: http 客户端/服务器搭建, json 序列化,文件压缩,热点管理,断点续传,,读写锁(读共享 写互斥),单例模式


项目模块:

服务端:
数据管理模块: 内存中使用hash表存储提高访问效率,持久化使用文件存储管理备份数据
业务处理模块: 搭建 http 服务器与客户端进行通信处理客户端的上传,下载,查看请求,并支持断点续

热点管理模块: 对备份的文件进行热点管理,将长时间无访问文件进行压缩存储,节省磁盘空间。


客户端
数据管理模块: 内存中使用hash表存储提高访问效率,持久化使用文件存储管理备份数据
文件检索模块: 基于 c++17 文件系统库,遍历获取指定文件夹下所有文件。
文件备份模块: 搭建 http 客户端上传备份文件。

整体代码

服务器端(Linux下实现)

util.hpp(实用文件工具模块)


#ifndef _MY_UTIL_
#define _MY_UTIL_
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include<sys/stat.h>
#include"bundle.h"
#include<experimental/filesystem>
#include<jsoncpp/json/json.h>
#include<memory>namespace cloud
{namespace fs=std::experimental::filesystem;class FileUtil{private:std::string _filename;//文件名称public:FileUtil(const std::string &filename):_filename(filename)//构造函数 {}bool Remove()//删除文件{//若文件不存在 相当于删除成功 则返回trueif(this->Exists()==false){return true;}remove(_filename.c_str());return true;}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  LastMTime()//文件最后一次修改时间{struct stat st;if( stat(_filename.c_str(),&st)<0){std::cout<<"get file size failed!\n";return -1;}  return st.st_mtime; }time_t  LastATime()//文件最后一次访问时间{struct stat st;if( stat(_filename.c_str(),&st)<0){std::cout<<"get file size failed!\n";return -1;}return st.st_atime;}std::string FileName()//文件名称{// ./abc/test.txtsize_t pos=_filename.find_last_of("/");if(pos==std::string::npos){return _filename;}return _filename.substr(pos+1);}bool GetPostLen(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 file failed"<<std::endl;return false;}//打开成功,获取文件数据size_t fsize=this->FileSize();//获取文件大小if(pos+len>fsize)//若pos开始位置超过了文件大小{std::cout<<"get file len is error"<<std::endl;return false;}ifs.seekg(pos,std::ios::beg);//从文件起始位置偏移到pos位置处body->resize(len);ifs.read(&(*body)[0], len);//读取文件所有数据到 body中if(ifs.good()==false)//读取出错{std::cout<< "get file content fialed "<<std::endl;ifs.close();return false;}ifs.close();return true;}bool GetContent(std::string *body) //获取整体文件数据{size_t fsize=this->FileSize();//获取文件大小return GetPostLen(body,0,fsize);}                                       bool SetContent(const std::string &body)//写入文件数据{ std::ofstream  ofs;ofs.open(_filename,std::ios::binary);  //以二进制方式打开文件if(ofs.is_open()==false)//打开失败{std::cout<<"write open file failed"<<std::endl;return false;}ofs.write(&body[0], body.size());//将body数据写入到文件中if(ofs.good()==false)//写入失败{std::cout<<"write file content failed"<<std::endl;ofs.close();  return false;}ofs.close();return true;}bool Compress(const std::string &packname) //压缩{//读取文件数据std::string body;if(this->GetContent(&body)==false){std::cout<<"compress get file content failed"<<std::endl;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"<<std::endl;return false;}return true;}bool UnCompress(const std::string &filename)//解压缩{//将当前压缩包数据读取出来std::string body;if(this->GetContent(&body)==false)//获取文件内容{std::cout<<"compress get file content failed"<<std::endl;return false;} //对压缩的数据进行解压缩std::string unpacked=bundle::unpack(body);//将解压缩的数据写入到新文件中FileUtil fu(filename);if(fu.SetContent(unpacked)==false)//写入数据失败{std::cout<<"uncompress write packed data failed"<<std::endl;return false;}return true;}bool Exists()//判断文件是否存在{return fs::exists(_filename);}bool CreateDirectory()//创建目录{if(this->Exists()){return true;}return fs::create_directories(_filename);}bool ScanDirectory(std::vector<std::string> * arry)//浏览目录{for (auto & p : fs::directory_iterator(_filename))//遍历目录{if(fs::is_directory(p)==true)//检测遍历到的文件 是一个文件夹 就不进行操作{continue;}//普通文件才进行操作//relative_path 表示带有路径的文件名arry->push_back(fs::path(p).relative_path().string());}return true;}};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;sw->write(root,&ss);*str=ss.str();return true;}// 反序列化 将字符串 转化为 结构化数据static bool UnSerialize(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<<"parse error:"<<err<<std::endl;return false;}return true;}};
}#endif

service.hpp(业务处理模块)


#ifndef  _MY_SERVICE_//避免头文件重复包含
#define  _MY_SERVICE_
#include "data.hpp"
#include "httplib.h"extern cloud::DataManager *_data;
namespace cloud
{class Service{private:int _server_port;std::string _server_ip;std::string _download_prefix;httplib::Server _server;private:static void Upload(const httplib::Request &req,httplib::Response &rsp)//上传请求{// post /upload 文件数据在正文中auto ret=req.has_file("file");//判断有没有上传的文件区域if(ret==false){rsp.status=400;return;}//若有上传的文件区域 则获取文件包含的各项数据const auto& file =req.get_file_value("file");//file.filename文件名称   file.content 文件内容std::string back_dir= Config::GetInstance()->GetBackDir();std::string realpath=back_dir+FileUtil(file.filename).FileName();FileUtil fu(realpath);fu.SetContent(file.content);//将数据写入文件中BackupInfo info;info.NewBackupInfo(realpath);//组织备份文件信息_data->Insert(info);//向数据管理模块添加备份文件信息return;}static std::string TimetoStr(time_t  t){std::string tmp=std::ctime(&t);return tmp;}static void ListShow(const httplib::Request &req,httplib::Response &rsp)// 获取展示页面处理{//获取所有文件信息std::vector<BackupInfo> arry;_data->GetAll(&arry);//根据所有备份信息 组织html文件数据std::stringstream ss;ss << "<html><head><title>Download</title><meta charset='UTF-8'></head>";ss<<"<body><hl>Download</hl><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>";rsp.body=ss.str();rsp.set_header("Content-Type","text/html");//设置头部信息rsp.status=200;//状态码设置为200 表示成功响应return ;}static std::string GetETag(const  BackupInfo & info)//获取 ETag字段{//  etag 文件名 - filename-fsize 文件大小 - mtime 最后一次修改时间 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 &rsp)//文件下载请求{// 获取客户端请求的资源路径 req.path// 根据资源路径  获取文件备份信息BackupInfo info;_data->GetOneByURL(req.path,&info);//通过URL获取单个数据//判断文件是否被压缩  如果被压缩 需要先解压缩if(info.pack_flag==true)//说明被压缩了{FileUtil fu(info.pack_path); //使用压缩包路径实例化一个对象fu.UnCompress(info.real_path);//解压缩// 删除压缩包  修改备份信息fu.Remove();//删除文件info.pack_flag=false;  _data->Update(info);//更新}// 读取文件数据 放入 rsp.body中FileUtil fu(info.real_path);fu.GetContent(&rsp.body);//将 real_path中的数据 放入 body中bool retrans =false;//retrans表示断点续传std::string old_etag;if(req.has_header("If-Range"))//若存在该头部字段 则说明为断点续传{old_etag =req.get_header_value("If-Range");//将头部字段对应的value值传给 old_etagif(old_etag ==GetETag(info))//若If-Range字段 的值 与请求文件的最新etag一致 则符合断点续传{retrans =true;}}//若没有 If-Range 字段 则为正常下载//如果有这个字段 但是它的值与当前文件的 etag 不一致 则必须返回全部数据if(retrans==false){//正常下载fu.GetContent(&rsp.body);//将 real_path中的数据 放入 body中//设置响应头部字段  ETag  Accept-Ranges: bytesrsp.set_header("Accept-Ranges","bytes");//建立头部字段rsp.set_header("ETag",GetETag(info));rsp.set_header("Content-Type","application/octet-stream");rsp.status=200;//响应成功} else {//说明是断点续传 //httplib 内部实现对于 断点续传请求的处理//只需要用户 将文件所有数据读取到rsp.body中//内部会自动根据请求区间 从body中取出指定区间 数据 进行响应fu.GetContent(&rsp.body);//将 real_path中的数据 放入 body中rsp.set_header("Accept-Ranges","bytes");//建立头部字段rsp.set_header("ETag",GetETag(info));rsp.set_header("Content-Type","application/octet-stream");rsp.status=206;  //区间请求响应  }}public:Service()//构造函数{Config * config=Config::GetInstance();//创建对象_server_port=config->GetServerPort();_server_ip=config->GetServerIp();_download_prefix =config->GetDownloadPrefix();}bool RunModule()//运行模块{_server.Post("/upload",Upload);_server.Get("/listshow", ListShow);_server.Get("/", ListShow);std::string download_url =_download_prefix+"(.*)";//下载请求_server.Get(download_url,Download);_server.listen(_server_ip.c_str(),_server_port);return true;} };
}
#endif 

makefile

.PHONY: cloud
cloud:cloud.cpp  util.hpp g++ -g  $^ -o $@  -L./lib -lpthread  -lstdc++fs -ljsoncpp -lbundle

hot.hpp(热点管理模块)


#ifndef _MY_HOT_
#define _MY_HOT_
#include<unistd.h>
#include"data.hpp"extern cloud::DataManager *_data;
namespace cloud
{class HotManager{private:std::string _back_dir;// 备份文件路径std::string _pack_dir;// 压缩文件路径std::string _pack_suffix;//压缩包后缀名int _hot_time; // 热点时间private:bool Hotjudge(const std::string &filename)//热点判断{FileUtil fu(filename);time_t last_atime=fu.LastATime();//文件最后一次访问时间time_t cur_time=time(NULL);//当前系统时间if(cur_time-last_atime>_hot_time){//差值超过设定的值 所以为非热点文件return true;}//差值小于设定的值 所以为热点文件return false;}public:HotManager() //构造函数{Config* config=Config::GetInstance();//创建对象_back_dir=config->GetBackDir();_pack_dir=config->GetPackDir();_pack_suffix=config->GetPackFileSuffix();_hot_time=config->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)==false)//说明为热点文件{//热点文件不需要处理 所以直接跳过continue;}//获取文件备份信息cloud::BackupInfo bi;//GetOneByRealPat 通过realpath获取单个数据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);//将bi更新到_table哈希表中}usleep(1000);}return true;}};}#endif

data.hpp (数据管理模块)


#ifndef _MY_DATA_
#define _MY_DATA_
#include<unordered_map>
#include<pthread.h>
#include"config.hpp"namespace cloud
{typedef struct BackupInfo{bool pack_flag;//压缩标志size_t fsize;  //文件大小time_t mtime;  //最后一次修改时间time_t atime;  //最后一次访问时间std::string real_path;//文件实际存储路径std::string pack_path;//压缩包存储路径名称std::string url; //请求资源路径bool  NewBackupInfo(const std::string &realpath)//获取各项属性信息{FileUtil fu(realpath);if(fu.Exists()==false){std::cout<<"new backupinfo file not exists" <<std::endl;return false;}Config* config=Config::GetInstance();//创建对象std::string packdir=config->GetPackDir();//压缩包存放路径std::string packsuffix=config->GetPackFileSuffix();//压缩包后缀名称std::string download_prefix =config->GetDownloadPrefix();//URL前缀路径this->pack_flag=false;this->fsize=fu.FileSize();this->mtime=fu.LastMTime();this->atime=fu.LastATime();this->real_path=realpath; this->pack_path  = packdir+fu.FileName()+packsuffix;// ./backdir/a.txt -> ./packdir/a.txt.lzthis->url=download_prefix + fu.FileName();//./backdir/a.txt  -> /download/a.txt return true;}}BackupInfo;class DataManager{private:std::string _backup_file;//数据持久化存储文件 pthread_rwlock_t  _rwlock;//读写锁 std::unordered_map<std::string,BackupInfo> _table;//哈希表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)//通过URL获取单个数据{pthread_rwlock_wrlock(&_rwlock);//加写锁//因为url是key值 所以可以直接通过key值来进行查找auto it=_table.find(url);if(it==_table.end()){return false;}*info= it->second;//获取url对应的infopthread_rwlock_unlock(&_rwlock);//解锁return true;} bool GetOneByRealPath(const std::string &realpath ,BackupInfo*info)//通过realpath获取单个数据{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;this->GetAll(&arry);//获取所有数据放入arry中//添加到json::value中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["real_path"]=  arry[i].real_path;item["pack_path"]=  arry[i].pack_path;item["url"]= arry[i].url; root.append(item); //添加数组元素item       }  // 对json::value 序列化 std::string body;JsonUtil::Serialize(root,&body);//序列化 //写文件FileUtil fu(_backup_file);//数据持久化存储文件 fu.SetContent(body);return true;}bool InitLoad()//初始化加载{//将数据文件中的数据读取出来FileUtil fu(_backup_file);//数据持久化存储文件   if(fu.Exists()==false)//若没有数据 则直接返回true 就不用进行加载了{return true;}              std::string body;fu.GetContent(&body);//将_backup_file文件中的数据 全部读取到body中//反序列化Json::Value root;JsonUtil::UnSerialize(body,&root);//反序列化  将body字符串转化为 root结构化数据//将反序列化得到的Json::Value中的数据添加到table中for(int i=0;i<root.size();i++){BackupInfo info;info.pack_flag =root[i]["pack_flag"].asBool();info.fsize =root[i]["fsize"].asInt64();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;}};
}#endif

config.hpp(配置加载文件模块)


//防止头文件被重复包含
#ifndef _MY_CONFIG_
#define _MY_CONFIG_
#include"util.hpp"
#include<mutex>namespace cloud
{#define CONFIG_FILE "./cloud.conf"class Config{private:Config(){ReadConfigFile();//读取配置文件信息}static Config*    _instance;static std::mutex _mutex;private:int _hot_time;                //热点判断时间int _server_port;             //服务器的监听端口std::string _server_ip;       //下载的url前缀路径    std::string _download_prefix; // 压缩包后缀名称std::string _packfile_suffix; //备份文件存放目录std::string _pack_dir;        // 压缩包存放目录 std::string _back_dir;        // 服务器IP地址std::string _backup_file;     // 数据信息存放文件bool ReadConfigFile()//读取配置文件{FileUtil fu(CONFIG_FILE);std::string body;//获取文件内容到body中if(fu.GetContent(&body) ==false)//读取失败{std::cout<<"load config file failed"<<std::endl;return false;}Json::Value root;if(JsonUtil::UnSerialize(body,&root)==false)//反序列化 字符串转化为结构化数据 {std::cout<<"parse config file failed"<<std::endl;return false;}   _hot_time=root["hot_time"].asInt();_server_port=root["server_port"].asInt();_server_ip=root["server_ip"].asString();_download_prefix=root["download_prefix"].asString();_packfile_suffix=root["packfile_suffix"].asString();_pack_dir   =root["pack_dir"].asString();_back_dir   =root["back_dir"].asString();_backup_file=root["backup_file"].asString();return true;} 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() //IP地址{return _server_ip;}std::string GetDownloadPrefix()//URL前缀路径{return _download_prefix;}std::string GetPackFileSuffix()//压缩包后缀名称{return _packfile_suffix;}std::string GetPackDir() //压缩包存放路径{return _pack_dir;}std::string GetBackDir()//备份文件存放目录{return  _back_dir;}std::string GetBackupFile()//数据信息存放文件{return _backup_file;}};  Config* Config::_instance=NULL;std::mutex Config::_mutex;
}#endif

cloud.conf

{"hot_time": 30,  "server_port":9090, "server_ip":"10.0.16.6","download_prefix":"/download/","packfile_suffix":".lz","pack_dir": "./packdir/","back_dir": "./backdir/","backup_file":"./cloud.dat"
}

客户端(VS2022下实现)

util.hpp(实用文件工具模块)


#ifndef _MY_UTIL_
#define _MY_UTIL_
#include<iostream>
#include<fstream>
#include<string>
#include<vector>
#include<sys/stat.h>
#include<experimental/filesystem>
#include<memory>namespace cloud
{namespace fs = std::experimental::filesystem;class FileUtil{private:std::string _filename;//文件名称public:FileUtil(const std::string& filename) :_filename(filename)//构造函数 {}bool Remove()//删除文件{//若文件不存在 相当于删除成功 则返回trueif (this->Exists() == false){return true;}remove(_filename.c_str());return true;}size_t FileSize()//文件大小{struct stat st;if (stat(_filename.c_str(), &st) < 0){std::cout << "get file size failed!\n";return 0;}return st.st_size;}time_t  LastMTime()//文件最后一次修改时间{struct stat st;if (stat(_filename.c_str(), &st) < 0){std::cout << "get file size failed!\n";return -1;}return st.st_mtime;}time_t  LastATime()//文件最后一次访问时间{struct stat st;if (stat(_filename.c_str(), &st) < 0){std::cout << "get file size failed!\n";return -1;}return st.st_atime;}std::string FileName()//文件名称{// ./abc/test.txtsize_t pos = _filename.find_last_of("\\");if (pos == std::string::npos){return _filename;}// return  fs::path(_filename).filename().string();return _filename.substr(pos + 1);}bool GetPostLen(std::string* body, size_t pos, size_t len)//获取文件数据{//打开成功,获取文件数据size_t fsize = this->FileSize();//获取文件大小if (pos + len > fsize)//若pos开始位置超过了文件大小{std::cout << "get file len is error" << std::endl;return false;}std::ifstream ifs;ifs.open(_filename, std::ios::binary);//以二进制方式打开文件if (ifs.is_open() == false){std::cout << "read file failed" << std::endl;return false;}ifs.seekg(pos, std::ios::beg);//从文件起始位置偏移到pos位置处body->resize(len);ifs.read(&(*body)[0], len);//读取文件所有数据到 body中if (ifs.good() == false)//读取出错{std::cout << "get file content fialed " << std::endl;ifs.close();return false;}ifs.close();return true;}bool GetContent(std::string* body) //获取整体文件数据{size_t fsize = this->FileSize();//获取文件大小return GetPostLen(body, 0, fsize);}bool SetContent(const std::string& body)//写入文件数据{std::ofstream  ofs;ofs.open(_filename, std::ios::binary);  //以二进制方式打开文件if (ofs.is_open() == false)//打开失败{std::cout << "write open file failed" << std::endl;return false;}ofs.write(&body[0], body.size());//将body数据写入到文件中if (ofs.good() == false)//写入失败{std::cout << "write file content failed" << std::endl;ofs.close();return false;}ofs.close();return true;}bool Exists()//判断文件是否存在{return fs::exists(_filename);}bool CreateDirectory()//创建目录{if (this->Exists()){return true;}return fs::create_directories(_filename);}bool ScanDirectory(std::vector<std::string>* arry)//浏览目录{this->CreateDirectory();//创建目录for (auto& p : fs::directory_iterator(_filename))//遍历目录{if (fs::is_directory(p) == true)//检测遍历到的文件 是一个文件夹 就不进行操作{continue;}//普通文件才进行操作//relative_path 表示带有路径的文件名arry->push_back(fs::path(p).relative_path().string());}return true;}};
}#endif

data.hpp (数据管理模块)

#ifndef _MY_DATA_
#define _MY_DATA_
#include<unordered_map>
#include<sstream>
#include"util.hpp"namespace cloud
{class DataManager{private:std::string _backup_file;//备份信息的持久化存储文件std::unordered_map<std::string, std::string> _table;//哈希表public:DataManager(const std::string &backup_file)//构造函数:_backup_file(backup_file){InitLoad();}bool Storage()//持久化存储{//获取所有的备份信息std::stringstream ss;//字符串流auto it = _table.begin();for (; it != _table.end(); it++)//遍历_table{//将所有信息进行指定持久化格式的组织ss << it->first << " " << it->second << "\n";}//持久化存储FileUtil fu(_backup_file);fu.SetContent(ss.str());//将ss流中的数据写入到fu中return true;}//分割字符串数据int Split(const std::string& str, const std::string& sep, std::vector<std::string>* arry){int count = 0;size_t pos = 0;//分隔符位置size_t idx = 0;//起始偏移量while (1){//从起始偏移量开始的 分隔符位置pospos = str.find(sep, idx);if (pos == std::string::npos)//找不到分隔符就退出循环{break;}if (pos == idx)//若有两个以上分隔符 则跳过分隔符则为起始偏移量{idx = pos + sep.size();continue;}std::string tmp = str.substr(idx, pos - idx);//tmp为获取数据大小arry->push_back(tmp);idx += pos + sep.size();count++;}//最后一个数据也需放入arry数组中if (idx < str.size()){arry->push_back(str.substr(idx));count++;}return count;}bool InitLoad()//初始化加载{//从文件中读取所有数据FileUtil fu(_backup_file);std::string body;fu.GetContent(&body);//将数据读取到body中//进行数据解析 添加到表中std::vector<std::string>arry;Split(body,"\n",&arry);//分割字符串数据 放入arry数组中for (auto& a : arry){//在arry数组中 的每一个元素 分为  文件名 唯一标识std::vector<std::string>tmp;Split(a, " ", &tmp);//分割出文件名和唯一标识if (tmp.size() != 2){continue;}//tmp[0]文件名  tmp[1]唯一标识_table[tmp[0]] = tmp[1];//使哈希表中文件名和唯一标识一一对应}  return true;}bool Insert(const std::string &key,const std::string &val)//插入数据{_table[key] = val;Storage();return true;}bool Update(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;}};
}#endif

cloud.hpp (文件备份模块)

#ifndef _MY_CLOUD_  //防止头文件重复包含
#define _MY_CLOUD_ 
#include"data.hpp"
#include"httplib.h"
#include<windows.h>namespace cloud
{#define  SERVER_ADDR "192.144.206.100"#define   SERVER_PORT 9090class Backup{private:std::string  _back_dir;DataManager* _data;public:Backup(const std::string& back_dir, const std::string& back_file)//构造函数:_back_dir(back_dir){_data = new DataManager(back_file);}std::string GetFileIdentifier(const std::string &filename)//创建文件唯一标识{//a.txt -fsize(文件大小)-mtime(最后一次修改时间)FileUtil fu(filename);std::stringstream ss;ss << fu.FileName() << "-" << fu.FileSize() << "-" << fu.LastMTime();return ss.str();}bool Upload(const std::string &filename)//文件上传{//获取文件数据FileUtil fu(filename);std::string body;fu.GetContent(&body);//读取文件数据到body中//搭建http客户端上传 文件数据httplib::Client client(SERVER_ADDR,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 = client.Post("/upload", items);//发送post请求 URL资源路径uploadif (!res || res->status != 200)//res不存在 或者状态码不为200{//失败return false;}return true;} bool IsNeedUpload(const std::string &filename)//判断一个文件是否需要上传{//需要上传的文件的判断条件 1.文件是新增的  2.不是新增的但是被修改过//若文件是新增的: 则只需看有没有历史备份信息 若有则为新增的 std::string id;if (_data->GetOneByKey(filename, &id) != false)//找到了对应的历史信息{std::string new_id = GetFileIdentifier(filename);//获取新的标识信息if (new_id == id)//与历史文件信息一致 则不需要上传{return false;}}//不是新增的但是被修改过:有历史信息	但是历史的唯一标识与当前最新的唯一标识不一致FileUtil fu(filename);//若3秒钟内被修改过 就认为文件还在修改中if (time(NULL) - fu.LastMTime() < 3){return false;}std::cout << filename << " need upload! \n";return true;}bool RunModule()//运行模块{while (1){//1.遍历指定文件夹中的所有文件FileUtil fu(_back_dir);std::vector<std::string>arry;fu.ScanDirectory(&arry);//将_back_dir中的数据传入arry数组中//2.逐个判断文件是否需要上传for (auto& a : arry){ if (IsNeedUpload(a) == false)//不需要上传{continue;}//3.如果需要上传 则上传文件if (Upload(a) == true){//若上传成功 则新增文件备份信息 // 备份信息: 文件名称  唯一标识_data->Insert(a, GetFileIdentifier(a));std::cout << a << "upload success!\n";}}Sleep(1);std::cout << "------loop end -----\n";}} };
}#endif

cloud.cpp


#include"util.hpp"
#include"data.hpp"
#include"cloud.hpp"#define BACKUP_FILE "./backup.dat"
#define BACKUP_DIR "./backup/"
int main()
{cloud::Backup backup(BACKUP_DIR, BACKUP_FILE); \backup.RunModule();return 0;
}

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

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

相关文章

2023年国赛试题:配置inux1 为 CA 服务器

试题内容:配置 linux1 为 CA 服务器,为 linux 主机颁发证书。证书颁发机构有 效期 10 年,公用名为 linux1.skills.lan。申请并颁发一张供 linux 服务器使用的证书,证书信息:有效期 =5 年,公用名=skills.lan, 国家=CN,省=Beijing,城市=Beijing,组织=skills,组织单位…

Apache Sqoop使用

1. Sqoop介绍 Apache Sqoop 是在 Hadoop 生态体系和 RDBMS 体系之间传送数据的一种工具。 Sqoop 工作机制是将导入或导出命令翻译成 mapreduce 程序来实现。在翻译出的 mapreduce 中主要是对 inputformat 和 outputformat 进行定制。 Hadoop 生态系统包括&#xff1a;HDFS、Hi…

Linux处理文件常见命令

目录 1 cp 2 rm 3 zip与unzip 3.1 zip 3.2 unzip 4 cd 5 ls 6 chmod 7 scp 7.1 文件在你操作的机器上&#xff0c;你要传给另一个机器 7.1.1 文件 7.1.2 文件夹 7.2 文件在另一个机器上&#xff0c;你要把文件搞到你操作的机器上 7.2.1 文件 7.2.…

Tekton — 通过tekton-operator部署tekton组件

文章目录 版本信息部署准备安装卸载tekton组件 Tektoncd Operator 作为一个 Kubernetes 的扩展&#xff0c;可以方便快捷地在 Kubernetes 集群上安装、升级和管理 Tekton Pipelines、Dashboard、Triggers 等组件。 那么本篇文章介绍在K8S集群中如何通过tekton-operator部署Tekt…

m1源码编译xgboost的动态链接库dylib

1、下载源码 git clone --recursive https://github.com/dmlc/xgboost cd xgboost拉取源码时候&#xff0c;一定要加"--recursive"这个命令。把它的字模块也要拉取下来&#xff0c;才能编译成功 2、安装c依赖 必要的依赖项(不然后续编译时报错)&#xff0c;包括CM…

VUE+THREE.JS 点击模型相机缓入查看模型相关信息

点击模型相机缓入查看模型相关信息 1.引入2.初始化CSS3DRenderer3.animate 加入一直执行渲染4.点击事件4.1 初始化renderer时加入监听事件4.2 触发点击事件 5. 关键代码分析5.1 移除模型5.2 创建模型上方的弹框5.3 相机缓入动画5.4 动画执行 1.引入 引入模型所要呈现的3DSprite…

cocos 关于多个摄像机,动态添加节点的显示问题,需要动态修改layer。(跟随摄像机滚动)(神坑官网也不说明一下)

参考文章&#xff1a;Cocos 3.x 层级Layer - 简书 2D镜头跟随应该怎么实现呢 - Creator 3.x - Cocos中文社区 关于多个摄像机&#xff0c;动态添加节点的显示问题&#xff0c;需要动态修改layer&#xff1f; 场景&#xff1a;在制作摄像机跟随角色移动功能时&#xff0c;新增…

2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-C卷

2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-C卷 2024 年甘肃省职业院校技能大赛中职组 电子与信息类“网络安全”赛项竞赛样题-C卷A模块基础设施设置/安全加固&#xff08;200分&#xff09;A 模块基础设施设置/安全加固&#xff08;200 分&am…

数据结构—二叉树

文章目录 10.二叉树(1).二叉树的基本概念(2).遍历#1.前序遍历#2.中序遍历#3.后序遍历#4.非递归中序遍历 (3).中序前/后序建树#1.中序前序遍历建树#2.中序后序遍历建树 (4).递归和二叉树基本操作#1.求树高#2.求结点数#3.求叶子结点数#4.复制树#5.判断两棵树是否相等 (5).特殊二叉…

用HeidiSQL在MySQL中新建用户

用HeidiSQL登录到MySQL数据库&#xff0c;注意登录的时候要使用有权限的用户&#xff1a; 选择工具-》用户管理&#xff1a; 点击左上角的“添加”&#xff1a; 输入用户名、密码&#xff0c;并且分配权限&#xff1a; 点击右边的“添加对象”&#xff1a; 可以根据自己…

数据库中的笛卡尔积:定义、生成与避免策略

笛卡尔积&#xff08;Cartesian Product&#xff09;是一个在数据库和数据仓库中常见的概念。它来源于数学中的集合论&#xff0c;主要用于描述两个集合中元素之间所有可能的配对情况。在数据库领域&#xff0c;当你在查询中连接两个表时&#xff0c;如果没有指定适当的连接条件…

解决git action发布报错:Input required and not supplied: upload_url

现象&#xff1a; 这个问题死活都找不到原因&#xff0c;后来打了一段调试的代码 - name: Debug Create Release Output run: | echo "Release ID: ${{ env.RELEASE_ID }}" echo "Release Upload URL: ${{ env.RELEASE_UPLOAD_URL }}" env: RELEASE_ID: ${…

inBuilder低代码平台新特性推荐-第十三期

各位知乎的友友们&#xff0c;大家好~ 今天来给大家介绍一下inBuilder低代码平台社区版中特性推荐系列第十三期——登录配置&#xff01; inBuilder低代码平台内置了多种表单登录方式&#xff1a;用户名密码、AD域、数字证书。用户可以通过系统的登录页面进行登录。登录界面样…

Ansys Speos SSS|执行 Camera Sensor模拟结果后处理

附件下载 联系工作人员获取附件 概述 本文是Speos Sensor System&#xff08;SSS&#xff09;的使用指南&#xff0c;这是一个强大的解决方案&#xff0c;用于camera sensor模拟结果的后处理。本文的目的是通过一个例子来理解如何正确使用SSS。当然本文描述的分析步骤适合任…

python 堆与栈

【一】堆与栈 【 1 】简介 栈&#xff08;stack&#xff09;&#xff0c;有些地方称为堆栈&#xff0c;是一种容器&#xff0c;可存入数据元素、访问元素、删除元素&#xff0c;它的特点在于只能允许在容器的一端&#xff08;称为栈顶端指标&#xff0c;英语&#xff1a;top&a…

1.qml-3D入门讲解介绍

本章我们来学习QML 3D教程&#xff0c;QML 3D能够支持windows linux等多平台跨平台并且显示效果大部分一致&#xff0c;非常方便&#xff0c;学习的qt版本最低为qt6.5。 要使用qml 3D类&#xff0c;需要导入QtQuick3D模块。 这是使用空间渲染器和场景图的 QML 前端。目前&…

(六)Tiki-taka算法(TTA)求解无人机三维路径规划研究(MATLAB)

一、无人机模型简介&#xff1a; 单个无人机三维路径规划问题及其建模_IT猿手的博客-CSDN博客 参考文献&#xff1a; [1]胡观凯,钟建华,李永正,黎万洪.基于IPSO-GA算法的无人机三维路径规划[J].现代电子技术,2023,46(07):115-120 二、Tiki-taka算法&#xff08;TTA&#xf…

360公司-2019校招笔试-Windows开发工程师客观题合集解析

360公司-2019校招笔试-Windows开发工程师客观题合集 API无法实现进程间数据的相互传递是PostMessage2.以下代码执行后,it的数据为(异常) std::list<int> temp; std::list<int>::iterator it = temp.begin(); it = --it; 3.API在失败时的返回值跟其他不一样是 …

【EI会议征稿】第五届人工智能、网络与信息技术国际学术会议(AINIT 2024)

第五届人工智能、网络与信息技术国际学术会议&#xff08;AINIT 2024&#xff09; 第五届人工智能、网络与信息技术国际学术会议&#xff08;AINIT 2024&#xff09;将于2024年3月22-24日在中国南京举行。本届会议将主要关注人工智能、网络与信息技术面临的新的挑战问题和研究…

服务器主机安全用什么防护软件好?

一直以来服务器是许多企业、机构和个人进行关键任务操作的基础&#xff0c;而保护服务器主机安全是一项重要的任务&#xff0c;其中使用高防ip进行保护是有效且实用的方法&#xff0c;因为服务器主机安全受到危害的影响是多方面的&#xff0c;这边对于这方面也是进行了一定的了…