云备份项目
Posted 李憨憨_
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了云备份项目相关的知识,希望对你有一定的参考价值。
云备份项目
文章目录
云备份的认识
自动将本地计算机上指定文件夹中需要备份的文件上传备份到服务器中。并且能够随时通过浏览器进行查看并且下载,其中下载过程支持断点续传功能,而服务器也会对上传文件进行热点管理,将非热点文件进行压缩存储,节省磁盘空间。
项目实现目标
服务端程序:部署在Linux服务器上
实现针对客户端请求的业务处理:文件的上传备份,以及客户端浏览器的查看以及下载功能。并且具有热点管理功能,将非热点文件压缩存储节省磁盘空间
客户端程序:部署在Windows客户机上
实现针对客户端主机上指定的文件夹中的文件,自动进行检测判断是否需要备份,需要则上传到服务器备份
模块划分
服务端:
网络通信模块:实现与客户端进行网络通信,并进行http协议数据解析
业务处理模块:明确客户端请求,并且进行对应的业务处理(上传文件,备份信息获取,文件下载)
数据管理模块:进行统一数据管理
热点管理模块:对服务器上备份的文件进行热点管理,将非热点文件进行压缩存储
在我们的服务端,首先有一个网络通信模块,这个模块可以实现与任意客户端,其中我们有两个客户端,一个是浏览器,一个是我们自己写的备份客户端,其中我们的备份客户端专门向我们的网络通信模块,也就是我们的服务器里边发送一个文件上传请求而浏览器给服务器发送的请求是备份信息查看请求以及文件下载请求。
在服务端接收到网络通信数据之后,在服务器里边有一个非常重要的模块:业务处理模块,它的功能就是,针对网络通信模块将数据接收上来,业务处理模块进行一个数据的分析,分析这个数据是一个什么样的请求,并且针对这个请求进行对应的一个业务处理。
在服务端还有一个模块,叫做数据管理模块,该模块是专门进行数据管理的网络通信模块拿到数据之后进行解析,解析完毕之后业务处理模块进行业务处理,业务处理的过程中就会涉及到对数据的访问,不能直接访问数据,而是通过数据管理模块获取到数据,对数据进行操作,不管是管理,存储,还是获取都由数据管理模块统一进行,只需要把请求发送过来就行了。
在服务端还有一个与业务处理模块并行运行是热点管理模块(服务器后台功能),专门检测服务器上边哪个文件的热度降低了,变成非热点文件了,把它压缩起来,如果业务处理模块有人要下载这个文件,先把文件解压缩之后再进行响应,毕竟它是一个非热点。
客户端:(备份客户端)
目录检测模块:遍历客户端主机上的指定文件夹,获取文件夹下所有的文件信息
数据管理模块:管理客户端所备份的所有文件信息
(判断一个文件是否需要备份:1. 历史备份信息中不存在,2. 历史备份信息存在但是不一致)
网络通信模块:搭建网络通信客户端,将需要备份的文件上传备份到服务器
第三方库认识
JSON认识
json 是一种数据交换格式,采用完全独立于编程语言的文本格式来存储和表示数据。
例如:小明同学的学生信息
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数据对象类
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;//转float
bool asBool() const;//转 bool
;
Json::Value类:jsoncpp库与外界进行数据交互的中间数据类
如果要将多个和数据对象进行序列化,则需要先实例化一个Json::Value对象,将数据加入其中
Json::Write类:jsoncpp库的一个序列化类
成员接口:write()接口就是用于将Json::Value对象中的所有数据组织序列化成为一个字符串。
Json::Reader类:jsoncpp库的一个反序列类
成员接口:parse()接口就是用于将一个json格式字符串,解析各个数据到Json::Value对象中
例:
这是一个序列化
#include <iostream>
#include <sstream>
#include <string>
#include <jsoncpp/json/json.h>
using namespace std;
void Serialize()
const char *name = "小明";
int age = 18;
float score[] = 77.5, 88, 99;
//进行序列化
//1.定义一个Json::Value对象,将数据填充进去
Json::Value val;
val["姓名"] = name;
val["年龄"] = age;
val["成绩"].append(score[0]);
val["成绩"].append(score[1]);
val["成绩"].append(score[2]);
//2.使用StreamWriter对象进行序列化
Json::StreamWriterBuilder swb;
Json::StreamWriter *sw = swb.newStreamWriter();//new一个StreamWriter对象
stringstream ss;
sw->write(val, &os);//实现序列化
cout << ss.str() << endl;
delete sw;
int main()
Serialize();
return 0;
接下来再看一个反序列化
void UnSerialize(string &str)
Json::CharReaderBuilder crb;
Json::CharReader *cr = crb.newCharReader();
Json::Value val;
string err;
bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), &val, &err);
if (ret == false)
cout << "UnSerialize failed:" << err << endl;
delete cr;
return ;
cout << val["姓名"].asString() << endl;
cout << val["性别"].asString() << endl;
cout << val["年龄"].asString() << endl;
int sz = val["成绩"].size();
for (int i = 0; i < sz; ++i)
cout << val["成绩"][i].asFloat() << endl;
delete cr;
return;
bundle文件压缩库
BundleBundle 是一个嵌入式压缩库,支持23种压缩算法和2种存档格式。使用的时候只需要加入两个文件bundle.h 和 bundle.cpp 即可。
我们先创建出一个100M大小的文件
dd if=/dev/zero of=./hello.txt bs=100M count=1
#include <iostream>
#include <fstream>
#include <string>
#include "bundle.h"
using namespace std;
//读取文件所有数据
bool Read(const string &name, string *body)
ifstream ifs;
ifs.open(name, ios::binary);//以二进制方式打开文件
if (ifs.is_open() == false)
cout << "open failed!\\n";
return false;
ifs.seekg(0, ios::end);//fseek(fp, 0, SEEK_END);
size_t fsize = ifs.tellg();//获取当前位置相对于文件起始位置的偏移量
ifs.seekg(0, ios::beg);//fseek(fp, 0, SEEK_SET);
body->resize(fsize);
ifs.read(&(*body)[0], fsize);//string::c_str() 返回值 const char*
if (ifs.good() == false)
cout << "read file failed!\\n";
ifs.close();
return false;
ifs.close();
return true;
//向文件写入数据
bool Write(const string &name, const string &body)
ofstream ofs;
ofs.open(name, ios::binary);//以二进制方式打开文件
if (ofs.is_open() == false)
cout << "open failed!\\n";
return false;
ofs.write(body.c_str(), body.size());
if (ofs.good() == false)
cout << "read file failed!\\n";
ofs.close();
return false;
ofs.close();
return true;
void Compress(const string &filename, const string &packname)
string body;
Read(filename, &body);//从filename文件中读取数据到body中
string packed = bundle::pack(bundle::LZIP, body);//对body中的数据进行lzip格式压缩,返回压缩后数据
Write(packname, packed);//将压缩后的数据写入到指定的压缩包中
void UnCompress(const string &filename, const string &packname)
string packed;
Read(packname, &packed);//从压缩包中读取压缩的数据
string body = bundle::unpack(packed);//对压缩的数据进行解压缩
Write(filename, body);//将解压缩胡的数据写入到新文件中
int main()
Compress("./hello.txt", "./hello.zip");
UnCompress("./hi.txt", "./hello.zip");
return 0;
要验证两个文件内容是否一致,那就计算两个文件的MD5值,进行对比是否一致
MD5:是一种散列算法,会根据数据进行大量运算,最终得到一个最终结果(字符串),两个文件只要文件内容稍有差异,则最终得到的MD5值都是完全不一样的
很明显我们可以看出,这两个文件是一致的
httplib库
httplib 库,一个 C++11 单文件头的跨平台 HTTP/HTTPS 库。安装起来非常容易。只需包含 httplib.h 在你的代码中即可。
httplib 库实际上是用于搭建一个简单的 http 服务器或者客户端的库,这种第三方网络库,可以让我们免去搭建服务器或客户端的时间,把更多的精力投入到具体的业务处理中,提高开发效率。
http协议是一个应用层协议,在传输层基于tcp实现传输—因此http协议本质上是应用层的数据格式
http协议格式:
请求:
请求首行:请求方法URL协议版本\\r\\n
头部字段:key:val\\r\\n的键值对
空行:\\r\\n-用于间隔头部与正文
正文:提交给服务器的数据
响应:
响应首行:协议版本 响应状态码 状态码描述\\r\\n
头部字段:key: val\\r\\n的键值对
空行:\\r\\n-用于间接头部与正文
正文:响应给客户端的数据
在httplib库中,有两个数据结构,用于存放请求与响应信息:struct Request & struct Response
线程池中的线程获取连接进行处理:
1.接收请求数据,并进行解析,将解析得到的请求数据放到了一个Request结构体变量req中;
2.根据请求信息(请求方法&资源路径),到映射表中查找有没有对应的处理函数,如果没有则返回404;
3.如果有对应映射信息-调用业务处理函数,将解析得到的req传入接口中,并且传入一个空的Response结构体变量rsp;
4.处理函数中会根据req中的请求信息进行对应的业务处理,并且在处理过程中想rsp结构体中添加响应信息;
5.等到处理函数执行完毕,则httplib得到了一个填充了响应信息的Response变量rsp;
6.根据rsp中的信息,组织一个http格式的响应,发送给客户端;
7.如果是短连接则关闭连接处理下一个,长连接则等待请求,超时则关闭处理下一个;
先看一个简单的文件上传的界面
<!--html是一个超文本标签语言,一个标签就可以理解是一个元素,一个控件-->
<html>
<body>
<!--form是一个表单域控件,当点击表单域提交按钮时,会将表单域中所有控件的数据进行组织提交-->
<!--action是本次请求的资源路径;method是请求的方法;enctype是编码类型,是数据的组织格式-->
<form action="http://192.168.122.000:9090/upload" method="post" enctype="multipart/form-data">
<div>
<input type="file" name="file"> <!--这是一个文件上传的选择框-->
</div>
<div>
<input type="submit" name="submit" value="上传"> <!--这是一个submit提交按钮-->
</div>
</form>
</body>
</html>
其数据组织格式是这样的
POST /upload HTTP/1.1
HOST: 192.168.122.130:9090
Connection: keep-alive
Content-Length: xxxxx
Content-Type: multipart/form-data; boundary=--xxxxxxxxxxxxxxxxxxxx
----xxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data; name='file' filename=''
选中的那个文件的文件数据
----xxxxxxxxxxxxxxxxxxxxxxxxxx
Content-Disposition: form-data; name='submit'
上传(上传按钮的value值)
----xxxxxxxxxxxxxxxxxxxxxxxxxx
httplib库搭建服务器
有了这么一个界面之后,我们就可以搭建http服务器了
#include "httplib.h"
using namespace std;
void Numbers(const httplib::Request &req, httplib::Response &rsp)
//这就是业务处理函数
rsp.body = req.path;
rsp.body += "-------------";
rsp.body += req.matches[1];
rsp.status = 200;
rsp.set_header("Content-Type", "text/plain");
void Upload(const httplib::Request &req, httplib::Response &rsp)
//req.files MultipartFormDataMap v--MultipartFormDataname, filename, content, content_type
for (auto &v : req.files)
cout << v.second.name << endl; //区域字段标识名
cout << v.second.filename << endl;//如果是文件上传则保存文件名称
cout << v.second.content << endl;//区域正文数据,如果是文件上传则是文件内容数据
int main()
//实例化server对象
httplib::Server server;
//添加映射关系--告诉服务器什么请求用哪个函数处理
//因为数字没法确定固定数据,因此实际上人家用的是正则表达式--匹配符合制定规则的数据
//正则表达式中 \\d 表示一个数字字符;+表示匹配前边的字符一次或多次;()表示单独捕捉括号内规则的数据
server.Get("/numbers/(\\\\d+)", Numbers);
server.Post("/upload", Upload);
//0.0.0.0表示服务器任意地址;虚拟机需要关闭防火墙;云服务器需要设置安全组开通端口
server.listen("0.0.0.0", 9090);
return 0;
我们可以把action中的资源路径改成我们自己的虚拟机或者云服务器地址,然后当我们点击选择,选择我们刚写的HTML文件,然后点击上传,就可以看到一下结果
httplib库搭建客户端
#include "httplib.h"
using namespace std;
int main()
httplib::Client client("192.168.19.xxx", 9090);
//Result Get(const char *path, const Headers &headers);
httplib::Headers header =
"connection", "close"
;
//Response *res
auto res = client.Get("/numbers/5678", header);
if (res && res->status == 200)
cout << res->body << endl;
//Result Post(const char *path, const MultiparFormDataItems &items);
httplib::MultipartFormDataItems items;
httplib::MultipartFormData item;
item.name = "file";
item.filename = "hi.txt";
item.content = "hello nihao";
item.content_type = "application/octet-stream";
items.push_back(item);
res = client.Post("/upload", items);
return 0;
项目实现
云备份服务端实现
网络通信模块:通过httplib搭建http客户端与服务器实现网络通信
业务处理模块:针对客户端请求进行处理(上传,下载,展示界面)
数据管理模块:统一数据管理
热点管理模块:找出备份文件中的非热点文件,进行压缩存储
一个文件如果是非热点文件,我们会进行压缩存储,但是不管是否压缩,当别人获取展示界面的时候,我们都需要给出上传的文件信息,并且当客户端要下载文件的时候,也能找到对应的压缩包,进行解压后,返回源文件数据(不能是压缩包)
数据管理模块
数据管理模块:统一数据管理
到底要管理什么数据:原文件名,原文件大小,原文件时间属性,对应的压缩包名称,压缩标志
上传的文件,最终是要能够在浏览器上进行查看并下载的,而浏览器界面上我们需要能够展示客户端曾经备份过的文件:原文件名,文件大小,文件备份时间
而一个非热点文件,如果被压缩了,获取到的大小就是压缩包大小,时间是压缩包的时间,然而页面上应该展示的是原文件的各项属性,而不是压缩包的属性
一个文件一旦压缩了,压缩包会被存放到压缩包路径下,原文件就会被删除
一个压缩包被解压缩,就应该把解压后的文件放到备份路径,压缩包应该被删除
要管理的数据信息已经确定了,问题是到底如何管理?
数据的管理分为两部分:
一部分是内存中的数据管理:查询效率更高—undered_mep(hash)
以上是关于云备份项目的主要内容,如果未能解决你的问题,请参考以下文章