传统企业建设网站的内容,精选网站建立 推广 优化,建立网站图片,网站建设制作设计优化文章目录 [toc] 1 :peach:云备份的认识:peach:1.1 :apple:功能了解:apple:1.2 :apple:实现目标:apple:1.3 :apple:服务端程序负责功能:apple:1.4 :apple:服务端功能模块划分:apple:1.5 :apple:客户端程序负责功能:apple:1.6 :apple:客户端功能模块划分:apple: 2 :peach:环境搭建… 文章目录 [toc] 1 :peach:云备份的认识:peach:1.1 :apple:功能了解:apple:1.2 :apple:实现目标:apple:1.3 :apple:服务端程序负责功能:apple:1.4 :apple:服务端功能模块划分:apple:1.5 :apple:客户端程序负责功能:apple:1.6 :apple:客户端功能模块划分:apple: 2 :peach:环境搭建:peach:2.1 :apple:gcc升级到7.3版本:apple:2.2 :apple:安装jsoncpp库:apple:2.3 :apple:下载bundle数据压缩库:apple:2.4 :apple:下载httplib库:apple: 3 :peach:第三方库的基本认识:peach:3.1 :apple:json:apple:3.1.1 :lemon:json认识:lemon:3.1.2 :lemon:json的使用:lemon: 3.2 :apple:bundle:apple:3.2.1 :lemon:bundle文件压缩库认识:lemon:3.2.2 :lemon:bundle的使用:lemon: 3.3 :apple:httplib:apple:3.3.1 :lemon:httplib认识:lemon:3.3.2 :lemon:httplib使用:lemon: 4 :peach:服务端工具类实现:peach:4.1 :apple:文件实用工具类设计:apple:4.2 :apple:json实用工具类设计:apple: 5 :peach:服务端配置信息模块实现:peach:5.1 :apple:系统配置信息:apple:5.2 :apple:测试系统配置信息类:apple: 6 :peach:服务端数据管理模块实现:peach:6.1 :apple:备份信息类的实现:apple:6.2 :apple:服务端数据管理模块实现:apple:6.3 :apple:验证服务端数据管理模块:apple: 7 :peach:服务端热点管理模块实现:peach:7.1 :apple:热点管理实现思路:apple:7.2 :apple:热点管理类的设计:apple:7.3 :apple:验证服务端热点管理模块:apple: 8 :peach:服务端业务处理模块实现:peach:8.1 :apple:网络通信接口设计:apple:8.2 :apple:业务处理类设计:apple:8.2.1 :lemon:upload:lemon:8.2.2 :lemon:list_show:lemon:8.2.3 :lemon:download:lemon: 9 :peach:服务端整体模块的测试:peach:10 :peach:客户端文件检测模块实现:peach:11 :peach:客户端数据管理模块实现:peach:12 :peach:客户端文件备份模块实现:peach:13 :peach:服务器与客户端联合测试:peach:14 :peach:项目总结:peach:
1 云备份的认识
1.1 功能了解
自动将本地计算机上指定文件夹中需要备份的文件上传备份到服务器中。并且能够随时通过浏览器进行查看并且下载其中下载过程支持断点续传功能而服务器也会对上传文件进行热点管理将非热点文件进行压缩存储节省磁盘空间。
1.2 实现目标
该云备份项目需要我们实现两端程序其中包括部署在用户机的客户端程序上传需要备份的文件以及运行在服务器上的服务端程序实现备份文件的存储和管理两端合作实现总体的自动云备份功能。
1.3 服务端程序负责功能
对客户端上传的文件进行备份存储能够对文件进行热点文件管理对非热点文件进行压缩存储节省磁盘空间支持客户端浏览器查看访问文件列表支持客户端浏览器下载文件并且下载支持断点续传。
1.4 服务端功能模块划分
配置信息模块负责将配置信息加载到程序中数据管理模块负责服务器上备份文件的信息管理热点管理模块负责文件的热点判断以及非热点文件的压缩存储业务处理模块针对客户端的各个请求进行对应业务处理并响应结果网络通信模块搭建网络通信服务器实现与客户端通信。
1.5 客户端程序负责功能
能够自动检测客户机指定文件夹中的文件并判断是否需要备份将需要备份的文件逐个上传到服务器。
1.6 客户端功能模块划分
文件检测模块遍历获取指定文件夹中所有文件路径名称数据管理模块负责客户端备份的文件信息管理通过这些数据可以确定一个文件是否需要备份网络通信模块搭建网络通信客户端实现将文件数据备份上传到服务器。 2 环境搭建
2.1 gcc升级到7.3版本
使用如下命令即可完成
sudo yum install centos-release-scl-rh centos-release-scl
sudo yum install devtoolset-7-gcc devtoolset-7-gcc-c
source /opt/rh/devtoolset-7/enable
echo source /opt/rh/devtoolset-7/enable ~/.bashrc2.2 安装jsoncpp库
使用如下命令
sudo yum install epel-release
sudo yum install jsoncpp-devel查看是否安装成功可以使用下面命令 ls /usr/include/jsoncpp/json/但是要注意centos版本不同有可能安装的jsoncpp版本不同安装的头文件位置也就可能不同了。在其他的版本下可能会直接没有json这个文件夹,但是我们安装成功后一定会得到下面的文件
2.3 下载bundle数据压缩库
命令
git clone https://github.com/r-lyeh-archived/bundle.git大家也可以到gitup下载【bundle】
2.4 下载httplib库
命令
git clone https://github.com/yhirose/cpp-httplib.gitgitup仓库地址【httplib】 3 第三方库的基本认识
3.1 json
3.1.1 json认识
在网络的学习中我们知道json是一种数据交换格式可以用来进行序列化与反序列化的是采用完全独于编程语言的文本格式来存储和表示数据除此之外我们常用的数据交换格式还有protobuf。
例如小明同学的学生信息
char name 小明;
int age 18;
float score[3] {88.5, 99, 58};
json这种数据交换格式是将这多种数据对象组织成为一个字符串
[{姓名 : 小明,年龄 : 18,成绩 : [88.5, 99, 58]},{姓名 : 小黑,年龄 : 18,成绩 : [88.5, 99, 58]}
]json数据类型对象数组字符串数字:
对象使用花括号 {} 括起来的表示一个对象;数组使用中括号 [] 括起来的表示一个数组;字符串使用常规双引号 括起来的表示一个字符串;数字包括整形和浮点型可以直接使用。
jsoncpp库用于实现json格式的序列化和反序列化完成将多个数据对象组织成为json格式字符串以及将json格式字符串解析得到多个数据对象的功能。 这其中主要借助三个类以及其对应的少量成员函数完成
//Json数据对象类
class Json::Value
{Value operator(const Value other); //Value重载了[]和因此所有的赋值和获取数据都可以通过[]和处理Value operator[](const std::string key);//简单的方式完成 val[姓名] 小明;Value operator[](const char* key);Value removeMember(const char* key);//移除元素const Value operator[](ArrayIndex index) const; //val[成绩][0]Value append(const Value value);//添加数组元素val[成绩].append(88); ArrayIndex size() const;//获取数组元素个数 val[成绩].size();std::string asString() const;//转string string name val[name].asString();const char* asCString() const;//转char* char *name val[name].asCString();Int asInt() const;//转int int age val[age].asInt();float asFloat() const;//转floatbool asBool() const;//转 bool
};//json序列化类低版本用这个更简单
class JSON_API Writer
{virtual std::string write(const Value root) 0;
}
class JSON_API FastWriter : public Writer
{virtual std::string write(const Value root);
}
class JSON_API StyledWriter : public Writer
{virtual std::string write(const Value root);
}
//json序列化类高版本推荐如果用低版本的接口可能会有警告
class JSON_API StreamWriter
{virtual int write(Value const root, std::ostream* sout) 0;
}
class JSON_API StreamWriterBuilder : public StreamWriter::Factory
{virtual StreamWriter* newStreamWriter() const;
}
//json反序列化类低版本用起来更简单
class JSON_API Reader
{bool parse(const std::string document, Value root, bool collectComments true);
}
//json反序列化类高版本更推荐
class JSON_API CharReader
{virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, std::string* errs) 0;
}
class JSON_API CharReaderBuilder : public CharReader::Factory
{virtual CharReader* newCharReader() const;
}在json的序列化类中我们可以看见实现了有低版本和高版本的方法但是我们强烈推荐使用高版本的成员方法。
3.1.2 json的使用
json实现序列化
#includeiostream
#includejsoncpp/json/json.h
#includesstream
#includestring
#includememoryint main()
{const char* name刘纯缘;int age21;float score[]{88.5,77.6,74.9};Json::Value val;val[姓名]name;val[年龄]age;val[得分].append(score[0]);val[得分].append(score[1]);val[得分].append(score[2]);Json::StreamWriterBuilder swb;std::unique_ptrJson::StreamWriter sw(swb.newStreamWriter());std::stringstream ss;sw-write(val,ss);std::coutss.str()std::endl;return 0;
}json实现反序列化
#includeiostream
#includejsoncpp/json/json.h
#includesstream
#includestring
#includememoryint main()
{std::string strR({姓名:刘纯缘, 年龄:21, 得分:[88.5,77.6,74.9]});Json::Value val;Json::CharReaderBuilder crb;std::unique_ptrJson::CharReader cr(crb.newCharReader());std::string err;cr-parse(str.c_str(),str.c_str()str.size(),val,err);std::coutval[姓名].asCString()std::endl;std::coutval[年龄].asInt()std::endl;//使用两种方式遍历int nval[得分].size();for(int i0; in; i)std::coutval[得分][i].asFloat()std::endl;for(auto itval[得分].begin(); it!val[得分].end(); it)std::coutit-asFloat()std::endl;return 0;
}注意点
1️⃣无论是序列化还是反序列化的时候我们使用g编译程序的时候我们都得加上ljsoncpp来连接第三方库。2️⃣在反序列化的时候第一行代码中R()是C11引入的专门处理解析时错误的解析为{为一个单独的字符串,我们当然也可以加上‘\’进行转义处理不过没这个使用方便。
3.2 bundle
3.2.1 bundle文件压缩库认识
bundle 是一个嵌入式压缩库支持23种压缩算法和2种存档格式。使用的时候只需要加入两个文件bundle.h 和 bundle.cpp 即可。
namespace bundle
{// low level API (raw pointers)bool is_packed( *ptr, len );bool is_unpacked( *ptr, len );unsigned type_of( *ptr, len );size_t len( *ptr, len );size_t zlen( *ptr, len );const void *zptr( *ptr, len );bool pack( unsigned Q, *in, len, *out, zlen );bool unpack( unsigned Q, *in, len, *out, zlen );// medium level API, templates (in-place)bool is_packed( T );bool is_unpacked( T );unsigned type_of( T );size_t len( T );size_t zlen( T );const void *zptr( T );bool unpack( T , T );bool pack( unsigned Q, T , T );// high level API, templates (copy)T pack( unsigned Q, T );T unpack( T );
}3.2.2 bundle的使用
bundle库实现文件压缩:
#include iostream
#include string
#include fstream
#include bundle.h
int main(int argc, char *argv[])
{if (argc ! 3){std::cout argv[1] 是原始文件路径名称\n;std::cout argv[2] 是压缩包名称\n;return -1;}std::string ifilename argv[1];std::string ofilename argv[2];std::ifstream ifs;ifs.open(ifilename, std::ios::binary); ifs.seekg(0, std::ios::end);size_t fsize ifs.tellg(); ifs.seekg(0, std::ios::beg); std::string body;body.resize(fsize); ifs.read(body[0], fsize); std::string packed bundle::pack(bundle::LZIP, body); //压缩文件std::ofstream ofs;ofs.open(ofilename, std::ios::binary); ofs.write(packed[0], packed.size()); ifs.close();ofs.close();return 0;
}当我们运行时
./test httplib.h httplib.lz
我们生成了httplib.lz文件后我们再解压然后对比源文件与解压后的文件的md5值就可以验证压缩与解压的正误。
不过这里我们使用Makefile时要注意连接pthread库以及将bundle.cpp添加编译。
bundle库实现文件解压缩:
int main(int argc, char* argv[])
{if(argc ! 3){std::cout argv[1] 是压缩包名称\n;std::cout argv[2] 是原始文件路径名称\n;return -1;}std::string ifilename argv[1];std::string ofilename argv[2];std::ifstream ifs;ifs.open(ifilename, std::ios::binary);ifs.seekg(0, std::ios::end);size_t fsizeifs.tellg();ifs.seekg(0, std::ios::beg);std::string body;body.resize(fsize);ifs.read(body[0],fsize);std::string unpacked bundle::unpack(body);//解压文件std::ofstream ofs;ofs.open(ofilename,std::ios::binary);ofs.write(unpacked[0],unpacked.size());ifs.close();ofs.close();return 0;
}当我们运行时
./test httplib.lz httplib-cp.h
接下来便是测试环节了 我们测试发现两个文件的md5sum值一模一样所以之前的代码应该是没啥问题的。
3.3 httplib
3.3.1 httplib认识
httplib库一个 C11 单文件头的跨平台 HTTP/HTTPS 库。安装起来非常容易。只需包含 httplib.h 在你的代码中即可。 httplib 库实际上是用于搭建一个简单的 http 服务器或者客户端的库这种第三方网络库可以让我们免去搭建服务器或客户端的时间把更多的精力投入到具体的业务处理中提高开发效率。
namespace httplib
{struct MultipartFormData {std::string name;std::string content;std::string filename;std::string content_type;};using MultipartFormDataItems std::vectorMultipartFormData;struct Request {std::string method;//请求方法std::string path;//资源路径Headers headers;//头部字段std::string body;//正文// for serverstd::string version;//协议版本Params params;//查询字符串MultipartFormDataMap files;//保存的是客户端上传的文件信息Ranges ranges;//实现断点续传的请求区间bool has_header(const char *key) const;std::string get_header_value(const char *key, size_t id 0) const;void set_header(const char *key, const char *val);bool has_file(const char *key) const;MultipartFormData get_file_value(const char *key) const;};struct Response {std::string version;int status -1;std::string reason;Headers headers;std::string body;std::string location; // Redirect locationvoid set_header(const char *key, const char *val);void set_content(const std::string s, const char *content_type);};class Server {using Handler std::functionvoid(const Request , Response );using Handlers std::vectorstd::pairstd::regex, Handler;//请求与处理函数的映射表std::functionTaskQueue *(void) new_task_queue;//线程池用于处理请求Server Get(const std::string pattern, Handler handler);Server Post(const std::string pattern, Handler handler);Server Put(const std::string pattern, Handler handler);Server Patch(const std::string pattern, Handler handler); Server Delete(const std::string pattern, Handler handler);Server Options(const std::string pattern, Handler handler);bool listen(const char *host, int port, int socket_flags 0);//搭建并启动http服务器};class Client {Client(const std::string host, int port);Result Get(const char *path, const Headers headers);Result Post(const char *path, const char *body, size_t content_length, const char *content_type);Result Post(const char *path, const MultipartFormDataItems items);//POST提交多区域数据常用于多文件上传}
}上面Request类的作用
1️⃣客户端保存有关http请求相关的信息,最终组织成http请求发送给服务器2️⃣服务器对收到的http请求进行解析将解析过的数据保存在Request类中,等待后续处理。
上面Response类的作用
用户将响应的数据放在Response类中httplib会按照其响应格式组织发送给客户端。
3.3.2 httplib使用
httplib库搭建简单服务器:
#includestring
#includeiostream
#includehttplib.hint main()
{httplib::Server ser;ser.Get(/hello, [](const httplib::Request req, httplib::Response rps){ rps.set_content(hello world, text/plain); });ser.Get(R(/numbers/(\d)), [](const httplib::Request req, httplib::Response rps){auto numbersreq.matches[1];//matches[0]是路径rps.set_content(numbers,text/plain); });ser.Post(/load, [](const httplib::Request req, httplib::Response rps){auto retreq.has_file(file);if(ret false){rps.status404;std::coutnot file loadstd::endl;return;}const auto filereq.get_file_value(file);rps.body.clear();rps.bodyfile.filename;rps.bodyfile.content;rps.bodyfile.content_type;rps.set_header(Content-Type,text/plain);rps.status200; });ser.listen(0.0.0.0,9090);return 0;
}注意使用httplib库编译时要连接pthread库。 我们启动服务器 然后在浏览器上来访问 httplib库搭建简单客户端:
#includestring
#includeiostream
#includehttplib.h#define SERVER_IP 8.137.105.247
#define SERVER_PORT 9090
int main()
{httplib::Client cli(SERVER_IP,SERVER_PORT);auto rescli.Get(/hello);std::coutres-statusstd::endl;std::coutres-bodystd::endl;res cli.Get(/numbers/123456);std::cout res-status std::endl;std::cout res-body std::endl;httplib::MultipartFormDataItems items {{file, this is file content, hello.txt, text/plain},};rescli.Post(/load,items);std::cout res-status std::endl;std::cout res-body std::endl;return 0;
}Makefile: 我们先启动服务端然后再启动客户端测试 从客户端的打印数据来看可以看出已经是验证成功的了。 4 服务端工具类实现
4.1 文件实用工具类设计
不管是客户端还是服务端文件的传输备份都涉及到文件的读写包括数据管理信息的持久化也是如此因此首先设计封装文件操作类这个类封装完毕之后则在任意模块中对文件进行操作时都将变的简单化。
类中实现的成员接口主要是获取文件最后一次修改时间获取文件最后一次访问时间获取文件大小删除文件获取文件名称读写文件(将文件中内容读到字符串中以及将字符串中内容写入文件)判断文件是否存在创建文件以及浏览文件压缩以及解压缩等。
其中值得注意的是我们判断文件是否存在创建文件以及浏览文件用的是C17提供的文件系统【C17文件系统】 编译时要记得连接stdcfs库。
namespace fs std::experimental::filesystem;
namespace grmcloud
{class FileUtil{public:FileUtil(const std::string path):_pathname(path){}int64_t getfile_size(){struct stat st;if(stat(_pathname.c_str(), st) 0){std::coutget file size failstd::endl;return -1;}return st.st_size;}time_t get_mtime() // 文件内容最后一次修改时间{struct stat st;if (stat(_pathname.c_str(), st) 0){std::cout get file mtime fail std::endl;return 0;}return st.st_mtim.tv_sec;}time_t get_atime()//文件最后一次访问时间{struct stat st;if (stat(_pathname.c_str(), st) 0){std::cout get file atime fail std::endl;return 0;}return st.st_atim.tv_sec;}bool remove_file(){if(exist() false)return true;remove(_pathname.c_str());}std::string get_filename(){auto pos_pathname.find_last_of(/);if(pos std::string::npos)return _pathname;return _pathname.substr(pos1);}bool get_pos_len(std::string body, size_t pos, size_t len){if(poslen getfile_size()){std::coutget_pos_len failstd::endl;return false;}std::ifstream ifs;ifs.open(_pathname.c_str(), std::ios::binary);if(ifs.is_open() false){std::coutread open file failstd::endl;return false;}ifs.seekg(pos, std::ios::beg);//从起始开始偏移到pos位置body.resize(len);ifs.read(body[0], len);if(ifs.good() false){std::coutread file failstd::endl;ifs.close();return false;}ifs.close();return true;}bool get_content(std::string body){return get_pos_len(body,0,getfile_size());}bool set_content(const std::string body){std::ofstream ofs;ofs.open(_pathname, std::ios::binary);if (ofs.is_open() false){std::cout write open file fail std::endl;return false;}ofs.write(body[0], body.size());if(ofs.good() false){std::coutwrite file failstd::endl;ofs.close();return false;}ofs.close();return true;}bool compress(const std::string packname)//压缩后文件的名字{//1将原文件的内容解析到body中std::string body;get_content(body);//2压缩body为unpackedstd::string packed bundle::pack(bundle::LZIP, body);//3将unpacked中的内容写到packname文件中FileUtil fu(packname);fu.set_content(packed);return true;}bool uncompress(const std::string unpackname){// 1将原文件的内容解析到body中std::string body;get_content(body);// 2解压缩body为packedstd::string unpacked bundle::unpack(body);// 3将unpacked中的内容写到packname文件中FileUtil fu(unpackname);fu.set_content(unpacked);return true;}//使用C17的filesystem要引入 -lstdcfsbool exist(){return fs::exists(_pathname);}bool create_directory(){return fs::create_directories(_pathname);}bool browse_directory(std::vectorstd::string vs)//浏览目录{for(auto p:fs::directory_iterator(_pathname)){//如果是目录就跳过if(fs::is_directory(p) true)continue;vs.push_back(fs::path(p).relative_path().string());}}private:std::string _pathname;};}我们可以设置一些简单的测试程序来验证上面的一些接口 测试:获取文件最后一次修改时间获取文件最后一次访问时间获取文件大小读写文件以及压缩和解压缩; std::string pathUtil.hpp;grmcloud::FileUtil file(path);std::coutfile.getfile_size()std::endl;std::coutfile.get_atime()std::endl;std::coutfile.get_mtime()std::endl;std::string body;file.get_content(body);grmcloud::FileUtil nfile(Util.txt);nfile.set_content(body);grmcloud::FileUtil fu1(Util.hpp);fu1.compress(Util.lz);grmcloud::FileUtil fu2(Util.lz);fu2.uncompress(Util-cp.txt);运行结果 验证md5sum值 不难发现基本上是没问题的。 测试浏览文件 我们先建立一个dir目录并向里面添加a.txtb.txtc.txtd.txt四个文件然后测试 grmcloud::FileUtil fu(dir);fu.create_directory();std::vectorstd::string vs;fu.browse_directory(vs);for (auto str : vs)std::cout str std::endl;测试结果 其实从之前使用bundle库的时候编译程序会比较慢大概要等个10秒左右我们其实完全可以把bundle.cpp打包成一个静态库。 具体方式可参考下面
gcc -c bundle.cpp
ar -rc libbundle.a bundle.o此时就生成了libbundle.a静态库: 此时我们删除bundle.cpp然后使用下面的Makefile编译程序
此时就会发现编译速度快了很多仍然可以得到正确的结果
4.2 json实用工具类设计
namespace grmcloud
{class JsonUtil{public:static bool serialize(const Json::Value root, std::string str){Json::StreamWriterBuilder swb;std::unique_ptrJson::StreamWriter sw(swb.newStreamWriter());std::stringstream ss;sw-write(root, ss);strss.str();return true;}static bool unserialize(const std::string str, Json::Value root){Json::CharReaderBuilder crb;std::unique_ptrJson::CharReader cr(crb.newCharReader());std::string err;cr-parse(str.c_str(), str.c_str() str.size(), root, err);return true;}};
}这个类很简单与我们之前讲解json的使用如出一辙这里就不再测试了。 5 服务端配置信息模块实现
5.1 系统配置信息
使用文件配置加载一些程序的运行关键信息可以让程序的运行更加灵活。 配置信息
热点判断时间文件下载URL前缀路径压缩包后缀名称上传文件存放路径压缩文件存放路径服务端备份信息存放文件服务器访问 IP 地址服务器访问端口
使用单例模式管理系统配置信息能够让配置信息的管理控制更加统一灵活,所以我们使用单例模式来管理配置信息的加载。
为了方便系统加载我们可以使用json来组织配置信息。创建一个系统配置文件Cloud.fig:
{hot_time : 30,server_ip : 8.137.105.247,server_port : 9090,url_prefix : /download/,pack_suffix : .lz,back_dir : ./backdir/,pack_dir : ./packdir/,server_backups : ./backups.data
}然后来实现Config类:
#define CONFIG Cloud.fig
namespace grmcloud
{class Config{public:static Config* get_instance(){if (_instance nullptr){if (_instance nullptr){_mutex.lock();_instance new Config;_mutex.unlock();}}return _instance;}time_t get_hottime(){return _hot_time;}std::string get_serverip(){return _server_ip;}int get_serverport(){return _server_port;}std::string get_urlprefix(){return _url_prefix;}std::string get_packsuffix(){return _pack_suffix;}std::string get_backdir(){return _back_dir;}std::string get_packdir(){return _pack_dir;}std::string get_server_backups(){return _server_backups;}private:time_t _hot_time;std::string _server_ip;int _server_port;std::string _url_prefix;//文件下载URL前缀路径,如/download/std::string _pack_suffix;//压缩包后缀名称,如.lzstd::string _back_dir;//上传文件存放路径std::string _pack_dir;//压缩文件存放路径std::string _server_backups;//服务端备份信息存放文件--配置文件如./backups.datastatic Config* _instance;static std::mutex _mutex;Config(){read_config();}Config(const Config con)delete;Config operator(const Config con)delete;void read_config(){//1将配置文件的信息读到body中FileUtil fu(CONFIG);std::string body;if(fu.get_content(body) false){std::coutget_content failstd::endl;return;}//2 将body中内容反序列化放进rootJson::Value root;if(JsonUtil::unserialize(body, root) false){std::coutunserialize failstd::endl;return;}//3将root中的信息传递给成员变量_hot_timeroot[hot_time].asInt();_server_iproot[server_ip].asString();_server_portroot[server_port].asInt();_url_prefixroot[url_prefix].asString();_pack_suffixroot[pack_suffix].asString();_back_dirroot[back_dir].asString();_pack_dirroot[pack_dir].asString();_server_backupsroot[server_backups].asString();}};Config* Config::_instancenullptr;std::mutex Config::_mutex;}单例模式的讲解我们这里用的是双重if判断来解决的博主讲解的上个项目日志系统直接用的是C11的静态变量是线程安全来处理的大家不要弄混了。
5.2 测试系统配置信息类 grmcloud::Config* confgrmcloud::Config::get_instance();std::coutconf-get_hottime()std::endl;std::coutconf-get_packdir()std::endl;std::coutconf-get_packsuffix()std::endl;std::coutconf-get_server_backups()std::endl;std::coutconf-get_serverip()std::endl;std::coutconf-get_serverport()std::endl;std::coutconf-get_backdir()std::endl;std::coutconf-get_urlprefix()std::endl;运行结果 可以看出是没有太大问题的。 6 服务端数据管理模块实现
6.1 备份信息类的实现
该类的主要作用是方便我们更好的管理备份信息 class BackUpInfor{public:BackUpInfor(const std::string realpath){FileUtil fu(realpath);if(fu.exist() false){//std::coutfile no existstd::endl;return;}_real_pathrealpath;_pack_flagfalse;_szfu.getfile_size();_atimefu.get_atime();_mtimefu.get_mtime();Config* confConfig::get_instance();std::string pack_dirconf-get_packdir();std::string filenamefu.get_filename();std::string url_prefixconf-get_urlprefix();std::string pack_suffixconf-get_packsuffix();//./backdir/a.txt - ./packdir/a.txt.lz_packpathpack_dirfilenamepack_suffix;//./backdir/a.txt - /download/a.txt_urlurl_prefixfilename;}bool _pack_flag;//文件是否被压缩标志size_t _sz;//文件大小time_t _atime;//文件最后一次访问时间time_t _mtime;//文件内容最后一次修改时间std::string _real_path;//文件实际存储路径std::string _packpath;//压缩包存储路径std::string _url;//文件访问url};6.2 服务端数据管理模块实现
内存中以文件访问URL为key数据信息结构为val使用哈希表进行管理查询速度快。使用url作为key是因为往后客户端浏览器下载文件的时候总是以 url 作为请求;采用文件形式对数据进行持久化存储序列化方式采用 json 格式或者自定义方式) class DataManager{public:DataManager(){_backups_fileConfig::get_instance()-get_server_backups();pthread_rwlock_init(_rwlock,nullptr);init_load();}~DataManager(){pthread_rwlock_destroy(_rwlock);}bool insert(const BackUpInfor infor){pthread_rwlock_wrlock(_rwlock);_hash[infor._url]infor;pthread_rwlock_unlock(_rwlock);storage();//一定要放在锁外面否则死锁return true;}bool update(const BackUpInfor infor){pthread_rwlock_wrlock(_rwlock);_hash[infor._url]infor;pthread_rwlock_unlock(_rwlock);storage();//一定要放在锁外面否则死锁return true;}bool get_one_by_url(const std::string url, BackUpInfor infor){pthread_rwlock_wrlock(_rwlock);auto res_hash.find(url);if(res ! _hash.end()){inforres-second;pthread_rwlock_unlock(_rwlock);return true;}pthread_rwlock_unlock(_rwlock);return false;}bool get_one_by_realpath(const std::string realpath, BackUpInfor infor){pthread_rwlock_wrlock(_rwlock);for(auto it:_hash){if(it.second._real_path realpath){infor it.second;pthread_rwlock_unlock(_rwlock);return true;}}pthread_rwlock_unlock(_rwlock);return false;}bool get_all(std::vectorBackUpInfor vp){pthread_rwlock_wrlock(_rwlock);for(auto it:_hash){vp.push_back(it.second);}pthread_rwlock_unlock(_rwlock);return true;}bool storage()//当有信息发生改变时(insert/update)时就需要持久化存储一次本质来说就是存储信息到配置文件中{//1 获得所有的数据管理信息std::vectorBackUpInfor vp;get_all(vp);//2 添加到Jsonval中Json::Value root;for(auto infor:vp){Json::Value tmp;tmp[pack_flag]infor._pack_flag;tmp[atime](Json::Int64)infor._atime;tmp[mtime](Json::Int64)infor._mtime;tmp[packpath]infor._packpath;tmp[real_path]infor._real_path;tmp[sz](Json::Int64)infor._sz;tmp[url]infor._url;root.append(tmp);}//3 序列化std::string body;JsonUtil::serialize(root, body);//4 将序列化后的数据写进配置文件中FileUtil fu(_backups_file);fu.set_content(body);return true;}bool init_load()//初始化程序运行时从配置文件读取数据{if (FileUtil(_backups_file).exist()){// 1 从配置文件读取消息到bodyFileUtil fu(_backups_file);std::string body;fu.get_content(body);// 2 反序列化Json::Value root;JsonUtil::unserialize(body, root);// 3 将反序列化后的Json::Value添加到_hash中for (int i 0; i root.size(); i){BackUpInfor tmp;tmp._pack_flag root[i][pack_flag].asBool();tmp._atime root[i][atime].asInt64();tmp._mtime root[i][mtime].asInt64();tmp._packpath root[i][packpath].asString();tmp._real_path root[i][real_path].asString();tmp._sz root[i][sz].asInt64();tmp._url root[i][url].asCString();insert(tmp);}}return true;}private:std::string _backups_file;//服务端备份信息存放文件std::unordered_mapstd::string , BackUpInfor _hash;//使用url与PackUpInfor建立映射pthread_rwlock_t _rwlock;//读写锁};注意点
1️⃣在进行数据操纵的时候我们使用的是读写锁而并非是互斥锁因为当我们只是想读取某个数据时而并不想要修改该数据时使用读写锁的效率会更加高效(读共享写互斥)2️⃣在插入或者修改时我们都要进行持久化存储(其本质就是更新配置文件中的信息),在初始化程序时我们也要能够从配置文件中读取数据。
6.3 验证服务端数据管理模块
测试程序
void test_packupinfor(const std::string realpath)
{std::coutinsertstd::endl;grmcloud::BackUpInfor pui(realpath);grmcloud::DataManager manager;manager.insert(pui);grmcloud::BackUpInfor tmp(Data.hpp);std::coutData.hpp 修改前的配置信息std::endl;std::cout tmp._pack_flag std::endl;std::cout tmp._atime std::endl;std::cout tmp._mtime std::endl;std::cout tmp._packpath std::endl;std::cout tmp._real_path std::endl;std::cout tmp._sz std::endl;std::cout tmp._url std::endl std::endl;std::coutData.hpp 修改后的配置信息(修改为Util.hpp的信息)std::endl;manager.get_one_by_url(/download/Util.hpp, tmp);std::cout tmp._pack_flag std::endl;std::cout tmp._atime std::endl;std::cout tmp._mtime std::endl;std::cout tmp._packpath std::endl;std::cout tmp._real_path std::endl;std::cout tmp._sz std::endl;std::cout tmp._url std::endlstd::endl;std::coutupdatestd::endl;pui._pack_flagtrue;manager.update(pui);std::vectorgrmcloud::BackUpInfor vp;manager.get_all(vp);for(auto v:vp){std::cout v._pack_flag std::endl;std::cout v._atime std::endl;std::cout v._mtime std::endl;std::cout v._packpath std::endl;std::cout v._real_path std::endl;std::cout v._sz std::endl;std::cout v._url std::endl std::endl;}std::coutstd::endl;std::coutget_one_by_realpathstd::endl;manager.get_one_by_realpath(realpath, tmp);std::cout tmp._pack_flag std::endl;std::cout tmp._atime std::endl;std::cout tmp._mtime std::endl;std::cout tmp._packpath std::endl;std::cout tmp._real_path std::endl;std::cout tmp._sz std::endl;std::cout tmp._url std::endl std::endl;
}
int main()
{test_packupinfor(Util.hpp);
}测试结果 从结果上来看应该是没有什么问题的。 7 服务端热点管理模块实现
7.1 热点管理实现思路
服务器端的热点文件管理是对上传的非热点文件进行压缩存储节省磁盘空间。 而热点文件的判断在于上传的文件的最后一次访问时间是否在热点判断时间之内比如如果一个文件一天都没有被访问过我们就认为这是一个非热点文件其实就是当前系统时间与文件最后一次访问时间之间的时间差是否在一天之内的判断。而我们需要对上传的文件每隔一段时间进行热点检测相当于遍历上传文件的存储文件夹找出所有的文件然后通过对逐个文件进行时间差的判断来逐个进行热点处理。 基于这个思想我们需要将上传的文件存储位置与压缩后压缩文件的存储位置分开。这样在遍历上传文件夹的时候不至于将压缩过的文件又进行非热点处理了。
关键点
上传文件有自己的上传存储位置非热点文件的压缩存储有自己的存储位置遍历上传存储位置文件夹获取所有文件信息获取每个文件最后一次访问时间进而完成是否热点文件的判断对非热点文件进行压缩存储删除原来的未压缩文件。
7.2 热点管理类的设计 class HotManager{public:HotManager(){Config* confConfig::get_instance();_hot_timeconf-get_hottime();_backdirconf-get_backdir();_packdirconf-get_packdir();_pack_suffixconf-get_packsuffix();//要记得创建目录FileUtil f1(_backdir);FileUtil f2(_packdir);f1.create_directory();f2.create_directory();}bool run_module(){while (true)//周而复始的运行{// 1 遍历备份目录获得所有的文件名称FileUtil fu(_backdir);std::vectorstd::string vs;fu.browse_directory(vs);// 2 判断文件是否是非热点文件for (auto name : vs){std::coutnamestd::endl;if (is_hotfile(name) false){BackUpInfor infor(name);if (_data-get_one_by_realpath(name, infor) false){// 文件存在但是却没有备份信息BackUpInfor tmp(name);infor tmp; // 设置新的备份信息}// 3 对非热点文件进行压缩FileUtil fna(name);fna.compress(infor._packpath); // 传入的是压缩后文件的名字// 4 删除源文件修改备份信息fna.remove_file();infor._pack_flag true; // 修改标志位表示已经压缩_data-update(infor);}}usleep(1000);}return true;}private:bool is_hotfile(const std::string name)//是热点文件返回true,否则返回false{FileUtil fu(name);time_t atimefu.get_atime();time_t curtimetime(nullptr);std::coutatime:curtimehot:_hot_timestd::endl;std::cout(curtime-atime)std::endl;if((curtime-atime) _hot_time)return false;return true;}time_t _hot_time;std::string _backdir;std::string _packdir;std::string _pack_suffix;};7.3 验证服务端热点管理模块
测试程序
grmcloud::DataManager* _data;
void test_hot()
{grmcloud::HotManager hot;hot.run_module();
}
int main(int argc, char*argv[])
{_datanew grmcloud::DataManager;test_hot();
}我们先拷贝httplib.h到backdir文件夹中 然后等待30s后 我们发现在backdir中的httplib.h已经消失而packdir文件夹中多了一个httplib.h.lz的压缩包。 8 服务端业务处理模块实现
云备份项目中 业务处理模块是针对客户端的业务请求进行处理并最终给与响应。而整个过程中包含以下要实现的功能
借助网络通信模块httplib库搭建http服务器与客户端进行网络通信针对收到的请求进行对应的业务处理并进行响应(文件上传列表查看文件下载(包含断点续传))
8.1 网络通信接口设计
业务处理模块要对客户端的请求进行处理那么我们就需要提前定义好客户端与服务端的通信明确客户端发送什么样的请求服务端处理后应该给与什么样的响应而这就是网络通信接口的设计。
HTTP文件上传 POST /upload HTTP/1.1 Content-Length11 Content-Typemultipart/form-data;boundary—WebKitFormBoundary16字节随机字符 ------WebKitFormBoundary Content-Dispositionform-data;filename“a.txt” hello world ------WebKitFormBoundary– HTTP/1.1 200 OK
Content-Length: 0HTTP文件列表获取 GET /list HTTP/1.1 Content-Length: 0 HTTP/1.1 200 OK
Content-Length:
Content-Type: text/html
htmlheadmeta http-equivContent-Type contenttext/html; charsetUTF-8 /titlePage of Download/title/headbodyh1Download/h1tabletrtda href/download/a.txt a.txt /a/tdtd alignright 1994-07-08 03:00 /tdtd alignright 27K /td/tr/table/body/htmlHTTP文件下载 GET /download/a.txt http/1.1 Content-Length: 0 HTTP/1.1 200 OK
Content-Length: 100000
ETags: filename-size-mtime一个能够唯一标识文件的数据
Accept-Ranges: bytes
文件数据这里面有一个字段是ETags,这个是资源的唯一标识当客户端第一次下载文件时就会收到这个信息当客户端再次下载时会先将该消息发送给服务器让其判断是否被修改如果没有就可以直接使用原先缓存的数据不用再重新下载了。 HTTP断点续传 GET /download/a.txt http/1.1 Content-Length: 0 If-Range: “文件唯一标识” Range: bytes89-999 HTTP/1.1 206 Partial Content
Content-Length:
Content-Range: bytes 89-999/100000
Content-Type: application/octet-stream
ETag: inodesizemtime一个能够唯一标识文件的数据
Accept-Ranges: bytes
对应文件从89到999字节的数据。If-Range字段是客户端告诉服务端是否支持断点续传 Accept-Ranges字段用于服务端告诉客户端支持断点续传单位是字节。
8.2 业务处理类设计 extern grmcloud::DataManager *_data;//因为业务处理的回调函数没有传入参数的地方因此无法直接访问外部的数据管理模块数据//可以使用lamda表达式解决但是所有的业务功能都要在一个函数内实现于功能划分上模块不够清晰//因此将数据管理模块的对象定义为全局数据,在这里声明一下就可以在任意位置访问了class Service{public:Service(){Config* confConfig::get_instance();_server_ipconf-get_serverip();_server_portconf-get_serverport();_download_prefixconf-get_urlprefix();}bool run_module(){_server.Post(/upload, upload);_server.Get(/listshow, list_show);_server.Get(/, list_show);std::string download_prefix_download_prefix(.*);_server.Get(download_prefix, download);_server.listen(0.0.0.0, _server_port);//云服务器的公网是一个子网共享的个人的机器是接受从公网ip转发的数据所以必须绑定0.0.0.0才行return true;}private:static void upload(const httplib::Request req, httplib::Response rsp)//上传文件{}static void list_show(const httplib::Request req, httplib::Response rsp){}static void download(const httplib::Request req, httplib::Response rsp){}std::string _server_ip;int _server_port;std::string _download_prefix;httplib::Server _server;};接下来我们便来实现上面类中函数。
8.2.1 upload static void upload(const httplib::Request req, httplib::Response rsp)//上传文件{//文件的数据是在正文中的但正文中还包括其他字段不仅仅是文件数据auto retreq.has_file(file);//判断是否有上传的文件区域(客户端与服务端要保持一致)if(ret false){std::coutno file uploadstd::endl;rsp.status404;return;}const auto filereq.get_file_value(file);std::string backdirConfig::get_instance()-get_backdir();std::string realpathbackdirFileUtil(file.filename).get_filename();FileUtil fu(realpath);fu.set_content(file.content);//将文件的数据写入到存储文件中BackUpInfor infor(realpath);_data-insert(infor);//将文件信息添加到数据管理的模块中(同时也增加了备份信息)}验证 我们新建立一个html文件具体源码参照下面(ps:博主不是搞前端的所以界面做的很简陋请见谅)
!DOCTYPE HTML
htmlbodyform actionhttp://8.137.105.247:9090/upload methodpost enctypemultipart/form-datadivinput typefile namefile/divdivinput typesubmit value上传/div/form/body
/html我们先上传文件: 然后再观察 我们发现文件已经上传成功了。
8.2.2 list_show
我们想要的界面很简单参考下面html代码
htmlheadtitleDownload/title/headbodyh1Download/h1tabletrtda href/download/test.txttest.txt/a/tdtd alignright 2021-12-29 10:10:10 /tdtd alignright 28k /td/tr/table/body
/htmllist_show实现: static void list_show(const httplib::Request req, httplib::Response rsp){//1 获取所有的文件备份信息std::vectorBackUpInfor vb;_data-get_all(vb);//2 根据备份信息来组织html数据std::stringstream ss;ss htmlheadtitleDownload/title/head;ss bodyh1Download/h1table;for (auto infor : vb){ss tr;std::string filename FileUtil(infor._real_path).get_filename();ss tda href infor._url filename /a/td;ss td alignright time_transfor(infor._mtime) /td;ss td alignright infor._sz / 1024 k/td;ss /tr;}ss /table/body/html;rsp.body ss.str();rsp.set_header(Content-Type, text/html);rsp.status 200;}static const char* time_transfor(time_t t){return std::ctime(t);}验证:
8.2.3 download static void download(const httplib::Request req, httplib::Response rsp){//1 获取客户端的资源路径根据资源路径来获取文件的备份信息//客户端的资源路径在req.path中BackUpInfor infor;_data-get_one_by_url(req.path, infor);//2 判断文件是否被压缩如果被压缩了就要先进行解压缩if(infor._pack_flag true){FileUtil fu(infor._packpath);fu.uncompress(infor._real_path);//将压缩文件解压到真实路径下fu.remove_file();//删除压缩包infor._pack_flagfalse;_data-update(infor);//更新配置信息}bool retrans false;std::string old_etag;if (req.has_header(If-Range)){old_etag req.get_header_value(If-Range);// 有If-Range字段且这个字段的值与请求文件的最新etag一致则符合断点续传if (old_etag get_etag(infor)){retrans true;}}//3 读取文件放进rsp的body中FileUtil fu(infor._real_path);fu.get_content(rsp.body);//4 设置响应头部字段 ETag Accept-Ranges: bytesrsp.set_header(Accept-Ranges, bytes);rsp.set_header(ETag, get_etag(infor));rsp.set_header(Content-Type, application/octet-stream);//这个字段必须有否则下载就会出问题if(retrans false)rsp.status 200;elsersp.status 206;}static std::string get_etag(const BackUpInfor infor)//格式文件名文件大小文件最近修改时间{std::string etaginfor._real_path;etag;etagstd::to_string(infor._sz);etag;etagstd::to_string(infor._mtime);return etag;}普通验证 我们将我们下载的文件与源文件进行比对 我们再来测试断点续传先删除刚才下载好的文件 测试方式为当我们下载一会儿时就立马关掉服务器然后再重启继续下载
终止服务器 下载网断了 重启服务器 继续下载 这样断点续传基本验证完毕了。 9 服务端整体模块的测试
在前面模块的实现中我们知道业务处理模块与热点管理模块都是死循环所以我们可以使用多线程来测试这两个模块。
grmcloud::DataManager* _data;
void test_hot()
{grmcloud::HotManager hot;hot.run_module();
}
void test_server()
{grmcloud::Service ser;ser.run_module();
}
int main(int argc, char*argv[])
{_datanew grmcloud::DataManager;std::thread hot_thread(test_hot);std::thread ser_thread(test_server);hot_thread.join();ser_thread.join();
}为了方便验证我们将backdir中文件清空然后重新上传文件 等待了30s后 非热点文件已经被压缩了。 10 客户端文件检测模块实现
为了让用户有更加好的体验客户端我们就在Windows下编写这样操作Windows的体验会对用户更加友好一些。 这个其实与服务端的文件实用工具类雷同只是功能需求并没有服务端那么多
#pragma once
#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
#includeiostream
#includestring
#includefstream
#includesys/stat.h
#includectime
#includeexperimental/filesystem
#includevectornamespace fs std::experimental::filesystem;
namespace grmcloud
{class FileUtil{public:FileUtil(const std::string path):_pathname(path){}int64_t getfile_size(){struct stat st;if (stat(_pathname.c_str(), st) 0){std::cout get file size fail std::endl;return -1;}return st.st_size;}time_t get_mtime() // 文件内容最后一次修改时间{struct stat st;if (stat(_pathname.c_str(), st) 0){std::cout get file mtime fail std::endl;return 0;}return st.st_mtime;}time_t get_atime()//文件最后一次访问时间{struct stat st;if (stat(_pathname.c_str(), st) 0){std::cout get file atime fail std::endl;return 0;}return st.st_atime;}bool remove_file(){if (exist() false)return true;remove(_pathname.c_str());}std::string get_filename(){auto pos _pathname.find_last_of(\\);if (pos std::string::npos)return _pathname;return _pathname.substr(pos 1);}bool get_pos_len(std::string body, size_t pos, size_t len){if (pos len getfile_size()){std::cout get_pos_len fail std::endl;return false;}std::ifstream ifs;ifs.open(_pathname.c_str(), std::ios::binary);if (ifs.is_open() false){std::cout read open file fail std::endl;return false;}ifs.seekg(pos, std::ios::beg);//从起始开始偏移到pos位置body.resize(len);ifs.read(body[0], len);if (ifs.good() false){std::cout read file fail std::endl;ifs.close();return false;}ifs.close();return true;}bool get_content(std::string body){return get_pos_len(body, 0, getfile_size());}bool set_content(const std::string body){std::ofstream ofs;ofs.open(_pathname, std::ios::binary);if (ofs.is_open() false){std::cout write open file fail std::endl;return false;}ofs.write(body[0], body.size());if (ofs.good() false){std::cout write file fail std::endl;ofs.close();return false;}ofs.close();return true;}//使用C17的filesystem要引入 -lstdcfsbool exist(){return fs::exists(_pathname);}bool create_directory(){return fs::create_directories(_pathname);}bool browse_directory(std::vectorstd::string vs)//浏览目录{//create_directory();for (auto p : fs::directory_iterator(_pathname)){//如果是目录就跳过if (fs::is_directory(p) true)continue;vs.push_back(fs::path(p).relative_path().string());}return true;}private:std::string _pathname;};
}
这里面值得注意的是在Windows中目录分割符用的是\,与Linux中使用的/不同。 11 客户端数据管理模块实现
这里为了简便实现客户端就不再使用像服务端那样从配置文件加载以及使用Json进行序列化和反序列化了而是直接使用\n作为序列化与反序列化时的分隔符。
namespace grmcloud
{class DataManager{public:DataManager(const std::string backupfile):_backupfile(backupfile){init_load();}bool insert(const std::string filename, const std::string identifi){_hash[filename] identifi;storage();return true;}bool update(const std::string filename, const std::string identifi){_hash[filename] identifi;storage();return true;}bool get_one_by_filename(const std::string filename, std::string identifi){auto res _hash.find(filename);if (res _hash.end())return false;identifi res-second;return true;}private:bool storage()//持久化存储{//1 读取所有的备份信息并组织格式化信息std::stringstream ss;for (auto e : _hash){ss e.first e.second \n;}//2 将格式化信息保存到_packdir文件中FileUtil fu(_backupfile);fu.set_content(ss.str());return true;}bool init_load(){//1 读取配置文件中的信息std::string body;FileUtil fu(_backupfile);fu.get_content(body);//2 解析body中的数据std::vectorstd::string vs;split(body, \n, vs);for (auto e : vs){std::vectorstd::string line;split(e, , line);if (line.size() ! 2)continue;_hash[line[0]] line[1];}return true;}size_t split(const std::string str, const std::string sep, std::vectorstd::string vs){int prev 0, cur 0;while (cur str.size()){cur str.find(sep, prev);if (cur prev){prev sep.size();continue;}std::string tmp str.substr(prev, cur - prev);//注意截取不包括sepvs.push_back(tmp);prev cur;cur sep.size();}return vs.size();}std::string _backupfile;std::unordered_mapstd::string, std::string _hash;};
}12 客户端文件备份模块实现
#pragma once
#includeData.hpp
#includehttplib.h
#includeWindows.h
#define SERVER_IP 8.137.105.247
#define SERVER_PORT 9090namespace grmcloud
{class Backup{public:Backup(const std::string backdir, const std::string backupfile):_backdir(backdir), _data(new DataManager(backupfile)){}~Backup(){delete _data;}bool upload(const std::string filename){std::string body;FileUtil fu(filename);fu.get_content(body);httplib::Client cli(SERVER_IP, SERVER_PORT);httplib::MultipartFormData item;item.content body;item.content_type application/octet-stream;item.filename fu.get_filename();item.name file;httplib::MultipartFormDataItems items;items.push_back(item);auto res cli.Post(/upload, items);if (!res || res-status ! 200)return false;return true;}void run_module(){while (true){FileUtil fu(_backdir);std::vectorstd::string vs;fu.browse_directory(vs);for (auto e : vs){if (check_upload(e)){if (upload(e)){_data-insert(e, trans_identifi(e));}}}/*for (auto e : vs){std::string ident trans_identifi(e);_data-insert(e, ident);}*/Sleep(1);}}private:std::string trans_identifi(const std::string filename){FileUtil fu(filename);std::stringstream ss;ss fu.get_filename() fu.getfile_size() fu.get_mtime();return ss.str();}bool check_upload(const std::string filename)//检查文件是否需要上传{std::string id;if (_data-get_one_by_filename(filename, id)){std::string new_id trans_identifi(filename);if (id new_id)return false;}//走到这里还要思考一个问题假如传送大文件会发生什么//由于大文件传送需要一定时间所以在传送过程中id会随着文件大小的变化而发生改变这样显然是不合理的//因为客户端会在传送完毕前一直向服务器传送文件//所以我们可以设定一个规定时间只要在规定时间内就认为该文件不需要上传FileUtil fu(filename);if (time(nullptr) - fu.get_mtime() 5)return false;//小于等于规定时间认为不用上传return true;}std::string _backdir;DataManager* _data;};
}里面需要注意的地方都写有注释。 13 服务器与客户端联合测试
我们先启动服务器然后再启动客户端 进入到VS中我们项目的目录中创建一个上传文件的目录。 然后我们复制一些文件到该目录下 此时我们观察客户端的备份信息 可以发现没有什么问题当过了30秒后我们在服务端观察
这3个文件已经全部被压缩了。 综上该验证是符合我们预期的。 14 项目总结
项目名称云备份系统项目功能搭建云备份服务器与客户端客户端程序运行在客户机上自动将指定目录下的文件备份到服务器并且能够支持浏览器查看与下载其中下载支持断点续传功能并且服务器端对备份的文件进行热点管理将长时间无访问文件进行压缩存储。 开发环境 centos7.9/vscode、g、gdb、makefile 以及 windows11/vs2022 技术特点 http客户端/服务器搭建 json序列化文件压缩热点管理断点续传线程池读写锁单例模式等。
项目模块 服务端
配置信息模块负责将配置信息加载到程序中数据管理模块负责服务器上备份文件的信息管理热点管理模块负责文件的热点判断以及非热点文件的压缩存储业务处理模块针对客户端的各个请求进行对应业务处理并响应结果网络通信模块搭建网络通信服务器实现与客户端通信。
客户端
文件检测模块遍历获取指定文件夹中所有文件路径名称数据管理模块负责客户端备份的文件信息管理通过这些数据可以确定一个文件是否需要备份网络通信模块搭建网络通信客户端实现将文件数据备份上传到服务器。
项目扩展
给客户端开发一个好看的界面让监控目录可以选择内存中的管理的数据也可以采用热点管理压缩模块也可以使用线程池实现实现用户管理不同的用户分文件夹存储以及查看实现断点上传客户端限速收费则放开。