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

文章目录

  • 客户端
    • 客户端实现思想
    • 客户端文件操作类的设计与拷贝
      • 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.mzph.cn/news/199124.shtml

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

相关文章

chineseocr项目不使用web推理-docker容器化

整个流程介绍 拉取 ufoym/deepo 镜像 -- 因为包含了主流深度学习框架&#xff0c;镜像4G出头。拉取 chineseocr 项目代码。修改代码&#xff0c;不使用web&#xff0c;增加命令行传入图片路径的功能打包成docker镜像。 开始 拉取 ufoym/deepo 镜像 &#xff1a;cpu版本为例 do…

设计必备网站,每天必看,无需翻墙。

设计师每天需要浏览各类设计互交网站&#xff0c;找素材、找灵感、看教程等等&#xff0c;下面就推荐几个非常好用的设计网站&#xff0c;我本人用了好几年&#xff0c;对广大设计师们一定有帮助&#xff0c;感觉收藏起来吧&#xff01; 1、免费设计素材——菜鸟图库 https://…

Java Websocket实现即时通讯功能入门教程

近年来&#xff0c;即时通讯&#xff08;Instant Messaging&#xff09;已经成为了人们日常生活中不可缺少的一部分。而Java Websocket技术则提供了一种简便高效的方式来实现即时通讯功能。本文将介绍如何使用Java Websocket来实现即时通讯&#xff0c;并提供具体的代码示例。 …

Vue自定义hook函数

hook 本质是一个函数&#xff0c;可以把 setup 函数中使用的 Composition API 进行封装。 hook 类似于 Vue2 中的 mixin 混合。 自定义 hook 的优势&#xff1a;复用代码&#xff0c;让 setup 中的逻辑更加清晰易懂。 自定义hook函数&#xff1a; 1、在 src 目录下创建 hooks…

跨境电商与本土文化融合:推动全球商业合作

随着全球经济的日益一体化&#xff0c;跨境电商成为推动全球商业合作的重要力量。在这个数字化时代&#xff0c;跨境电商不仅在商品流通上起到了桥梁作用&#xff0c;更在文化交流方面发挥了积极的作用。本文将深入探讨跨境电商如何与本土文化融合&#xff0c;以及这种融合如何…

GItLab项目导入到HBuilderX中,下载TortoiseGit 安装

1.项目拉取 选择你要下载的项目,选master下完后内部在切换想要分支,一般选http下载,下图我选的是ssh下载 选择导入,git导入 复制上地址 如果提示这个点击确定 找到下图位置安装 2.TortoiseGit 下载安装 TortoiseGit 官网下载地址&#xff1a;Download – TortoiseGit – Wind…

做一个类似东郊到家的上门服务类系统有哪些功能?

上门服务系统是一款便捷的技师接单、上门提供理疗服务的软件。我们拥有优秀的开发团队&#xff0c;为您量身定制解决方案&#xff0c;价格合理&#xff0c;用心服务。 预约上门&#xff1a;该功能是预约上门推拿理疗按摩系统软件小程序APP的核心功能。消费者通过系统预约下单&a…

待办事项app推荐哪一款?每日待办事项提醒用什么APP

每天的生活中&#xff0c;我们总是充满着各种待办事项&#xff0c;如果不及时处理&#xff0c;就会导致各种问题的出现。在众多的待办事项app中&#xff0c;如何选择一款最适合自己的app呢&#xff1f;所谓待办事项&#xff0c;通常是指尚未着手的事项。在日常生活中&#xff0…

求臻医学胃癌关爱日:美味的高“盐”值杀手

胃癌的发病率具有广泛的地域差异&#xff0c;在东南亚国家尤为高发。韩国是胃癌发病率排名第一的国家&#xff0c;其次为日本&#xff0c;中国紧随其后&#xff0c;由于中国人口基数大&#xff0c;其绝对患胃癌人数为全球第一&#xff0c;每年有100多万新诊断患者&#xff0c;其…

大文本限制录入文字后通过输入法鼠标单击还可继续超限额录入问题

textInpEl.on(keyup propertychange, "textarea", function () { var realMaxLength $(this).parent().parent().find(".maxNum").text(); //真实的最大长度 var endFontLen $(this).val().length; if (endFontLen < rea…

基于springboot的滑雪场管理系统源码

&#x1f345; 简介&#xff1a;500精品计算机源码学习&#xff0c;有8个项目关注搏主即可领取。另送简历模板、答辩模板、学习资料、答辩常见问题【关注我&#xff0c;都给你】 &#x1f345; 欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 文末获取源码 目录 一、以下学…

CentOS安装Docker

Docker 分为 CE 和 EE 两大版本。CE 即社区版&#xff08;免费&#xff0c;支持周期 7 个月&#xff09;&#xff0c;EE 即企业版&#xff0c;强调安全&#xff0c;付费使用&#xff0c;支持周期 24 个月。 Docker CE 分为 stable test 和 nightly 三个更新频道。 官方网站上…

[足式机器人]Part2 Dr. CAN学习笔记-数学基础Ch0-3线性化Linearization

本文仅供学习使用 本文参考&#xff1a; B站&#xff1a;DR_CAN Dr. CAN学习笔记-数学基础Ch0-3线性化Linearization 1. 线性系统 Linear System 与 叠加原理 Superposition2. 线性化&#xff1a;Taylor Series3. Summary 1. 线性系统 Linear System 与 叠加原理 Superposition…

智慧物联可视化大屏赋能设备管理和城市运行

在智慧物联的时代&#xff0c;万物互联的网络正在构筑起一个智能化的世界。无论是家居设备、汽车、还是工业设备&#xff0c;都能通过互联网实现智能化管理和控制。随着物联网技术的发展&#xff0c;我们迅速步入了一个千姿百态的智慧时代。智慧物联逐渐渗透进我们的日常生活&a…

Vmware虚拟机简介和安装

作者&#xff1a;余小小 常见的虚拟机 vmwarevirtualBox Vmware 运行在win系统上centos运行在Vm上 先安装vm&#xff0c;在安装centos系统 Vmware介绍 不用分区或者重开机&#xff0c;就可以在同一台pc上使用多种操作系统完全隔离&#xff0c;且保护不同的操作系统环境和文…

基于PIPNet的人脸106关键点检测

做美颜需要使用到人脸关键点&#xff0c;所以整理了一下最近的想法。 按模型结构分类&#xff1a; 1.Top-Down: 分为两个步骤&#xff0c;首先&#xff0c;对于原始输入图片做目标检测&#xff0c;比如做人脸检测&#xff0c;将人脸区域抠出&#xff0c;单独送进关键点检测模…

如何清理电脑缓存?简单几个步骤轻松搞定

清理电脑缓存的方法 下面我们为大家总结了一些可以用于清理电脑缓存的方法&#xff1a; 清理浏览器缓存 浏览器缓存是电脑为了我们能够更加快速的访问页面而临时保存的数据&#xff0c;随着时间的推移&#xff0c;浏览器缓存也会越来越多&#xff0c;这样不仅不会加快网页访…

广州华锐视点:VR仿真实训室中控系统成为VR课堂教学必备工具

随着科技的不断发展&#xff0c;虚拟现实&#xff08;VR&#xff09;技术已经逐渐走进我们的生活。从游戏娱乐到医疗教育&#xff0c;VR技术的应用范围日益广泛。近年来&#xff0c;VR技术在教育领域的应用也取得了显著的成果&#xff0c;为提高教育质量和培养创新人才提供了全…

24V-36v转3.3/5/9/12V芯片3A可调降压ic

一款高效、多功能的24V-36V转3.3/5/9/12V芯片3A可调降压IC 在当今电子设备日益普及的时代&#xff0c;电源管理显得尤为重要。为了满足各种设备的需求&#xff0c;一款高效、多功能的电源转换器成为工程师们的重要选择。本文将为您介绍一款24V-36V转3.3/5/9/12V芯片3A可调降压…

视频推拉流直播点播EasyDSS平台点播文件加密存储的实现方法

视频推拉流直播点播系统EasyDSS平台&#xff0c;可提供流畅的视频直播、点播、视频推拉流、转码、管理、分发、录像、检索、时移回看等功能&#xff0c;可兼容多操作系统&#xff0c;还能支持CDN转推&#xff0c;具备较强的可拓展性与灵活性&#xff0c;在直播点播领域具有广泛…