一、云备份认识
将本地计算机一个受监管的文件夹的文件上传到服务器中,有服务器组织,客户端可以通过网页将文件查看并且下载下来,下载过程支持断点续传功能,并且服务器会对上传的文件进行热点管理,长时间没人访问的文件夹会被压缩,以节省磁盘空间。
二、如何实现
这个项目需要我们在服务器与客户端两端都搭载程序,客户端负责上传、服务端负责管理。
三、服务端程序负责功能
- 针对客户端文件进行上传储存
- 能够对文件进行热点文件管理、对非热点文件压缩、节省磁盘空间
- 支持客户端访问查看文件列表
- 支持客户端浏览器下载文件、并实现断点续传
四、客户端程序负责功能呢
- 能够自动检测指定文件夹中的文件,并判断是否需要上传文件
- 将需要上传的文件组个上传
五、环境搭建
将服务器上的gcc替换成7.3版本,这里需要注意的是这个版本并不会有很强的向下兼容性------所以不能想着用更高的版本代替它,我们这里用的是7.3。这里默认的是centos的linux操作系统
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" >> ~/.bashrc
g++ -v //通过这个查看现在的版本是否正确
六、库的安装
我们需要安装jsoncpp库来实现序列化以及反序列化、bundle库来实现数据的压缩以及解压缩、httplib库搭建网络服务器接收请求
jsoncpp库
安装jsoncpp库:
sudo yum install epel-release
sudo yum install jsoncpp-devel
ls /usr/include/jsoncpp/json/
assertions.h config.h forwards.h reader.h version.h
autolink.h features.h json.h value.h writer.h
#注意,centos版本不同有可能安装的jsoncpp版本不同,安装的头文件位置也就可能不同了。
通过第三行的指令我们能看到安装下来的东西。
json的作用是将
cpp
char name = "小明";
int age = 18;
float score[3] = {88.5, 99, 58};
这样的数据,转换成:
cpp
[
{
"姓名" : "小明",
"年龄" : 18,
"成绩" : [88.5, 99, 58]
},
{
"姓名" : "小黑",
"年龄" : 18,
"成绩" : [88.5, 99, 58]
}
]
这样的数据,这个过程就是序列化。
序列化实现原理:
要实现序列化,我们首先需要一个容器来装载需要序列化的数据------也就是value,也被称为万能变量。
cpp
//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序列化类,低版本用这个更简单
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;
}
实现序列化:
cpp
int main()
{
const char *name = "小明";
int age = 18;
float score[] = {88.5, 98, 58};
Json::Value val;
val["姓名"] = name;
val["年龄"] = age;
val["成绩"].append(score[0]);
val["成绩"].append(score[1]);
val["成绩"].append(score[2]);
Json::StreamWriterBuilder swb;
std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
std::ostringstream os;
sw->write(val, &os);
std::string str = os.str();
std::cout << str <<std::endl;
return 0;
}
cpp
g++ json_example1.cpp -o json_example1 -ljsoncpp
经过序列化后的val值就是{"姓名":"小明", "年龄":18, "成绩":[76.5, 55, 88]}这样的。
实现反序列化:
cpp
int main()
{
std::string str = R"({"姓名":"小明", "年龄":18, "成绩":[76.5, 55, 88]})";
Json::Value root;
Json::CharReaderBuilder crb;
std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
std::string err;
cr->parse(str.c_str(), str.c_str() + str.size(), &root, &err);
std::cout << root["姓名"].asString() << std::endl;
std::cout << root["年龄"].asInt() << std::endl;
int sz = root["成绩"].size();
for (int i = 0; i < sz; i++) {
std::cout << root["成绩"][i].asFloat() << std::endl;
}
for (auto it = root["成绩"].begin(); it != root["成绩"].end(); it++){
std::cout << it->asFloat() << std::endl;
}
return 0;
}
反序列化就是将被限定格式的字符串变成一个个我们想要的数据。将root里面的数据像数组一样一个个取出来,归功于Value对于[]和=的重载。
序列化与反序列化的原理就是:自己规定一个字符串格式,然后将数据以这种格式储存起来。然后在想要取出数据的时候再凭借我们对这个格式的了解将其中的数据一个个取出来即可。
json库主要运用与服务器端,客户端需要储存的数据没有这么复杂,我们可以自己写一个序列化的操作练一下手。
bundle库
bundle库的安装:
bundle是一个嵌入式库,使用的时候只需要将bundle.cpp和bundle.hpp放入项目文件即可,可以从我的gitee上进行下载gitee项目地址
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 );
}
bundle的压缩:
cpp
#include <iostream>
#include <string>
#include <fstream>
#include "bundle.h"
int main(int argc, char *argv[])
{
std::cout <<"argv[1] 是原始文件路径名称\n";
std::cout <<"argv[2] 是压缩包名称\n";
if (argc < 3) 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);//调整body大小为文件大小
ifs.read(&body[0], fsize);//读取文件所有数据到body找给你
std::string packed = bundle::pack(bundle::LZIP, body);//以lzip格式压缩文件数据
std::ofstream ofs;
ofs.open(ofilename, std::ios::binary);//打开压缩包文件
ofs.write(&packed[0], packed.size());//将压缩后的数据写入压缩包文件
ifs.close();
ofs.close();
return 0;
}
bundle的解压缩:
cpp
#include <iostream>
#include <fstream>
int main(int argc, char *argv[])
{
if (argc < 3) {
printf("argv[1]是压缩包名称\n");
printf("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);
ifs.close();
std::string unpacked = bundle::unpack(body);//对压缩包数据解压缩
std::ofstream ofs;
ofs.open(ofilename, std::ios::binary);
ofs.write(&unpacked[0], unpacked.size());
ofs.close();
return 0;
}
需要注意的是:bundle库的压缩会使用到线程,所以在编译时需要加上调用的线程库:
cpp
g++ compress.cpp bundle.cpp -o compress -lpthread
g++ uncompress.cpp bundle.cpp -o uncompress -lpthread
./compress ./bundle.cpp ./bundle.cpp.lz
./uncompress ./bundle.cpp.lz ./bundle2.cpp
httplib库
httplib 库,一个 C++11 单文件头的跨平台 HTTP/HTTPS 库。安装起来非常容易。只需包含 httplib.h 在你的代码 中即可。
httplib 库实际上是用于搭建一个简单的 http 服务器或者客户端的库,这种第三方网络库,可以让我们免去搭建服务器或客户端的时间,把更多的精力投入到具体的业务处理中,提高开发效率。
cpp
namespace httplib{
struct MultipartFormData {
std::string name;
std::string content;
std::string filename;
std::string content_type;
};
using MultipartFormDataItems = std::vector<MultipartFormData>;
struct Request {
std::string method;
std::string path;
Headers headers;
std::string body;
// for server
std::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 location
void set_header(const char *key, const char *val);
void set_content(const std::string &s, const char *content_type);
};
class Server {
using Handler = std::function<void(const Request &, Response &)>;
using Handlers = std::vector<std::pair<std::regex, Handler>>;
std::function<TaskQueue *(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);
};
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);
}
}
前端测试页面:
XML
/* 前端测试页面 test.html 直接使用浏览器打开即可看到网页 */
<html>
<body>
<form action="http://服务器IP:端口/upload" method="post" enctype="multipart/form-data">
<input type="file" name="file">
<input type="submit" value="上传">
</form>
</body>
</html>
简易服务器的搭建:
cpp
#include "httplib.h"
int main(void)
{
using namespace httplib;
Server svr;
svr.Get("/hi", [](const Request& req, Response& res) {
res.set_content("Hello World!", "text/plain");
});
svr.Get(R"(/numbers/(\d+))", [&](const Request& req, Response& res) {
auto numbers = req.matches[1];
res.set_content(numbers, "text/plain");
});
svr.Post("/upload", [&](const auto& req, auto& res) {
auto size = req.files.size();
auto ret = req.has_file("file1");
const auto& file = req.get_file_value("file1");
std::cout << file.filename << std::endl;
std::cout << file.content_type << std::endl;
std::cout << file.content << std::endl;
});
svr.listen("0.0.0.0", 9090);
return 0;
}
//g++ -std=c++14 http_server.cpp -o http_server -lpthread
简易客户端:
cpp
#include "httplib.h"
#define SERVER_IP "你的服务器IP地址"
// HTTP
int main()
{
httplib::Client cli(SERVER_IP);
auto res = cli.Get("/hi");
std::cout << res->status << std::endl;
std::cout << res->body << std::endl;
auto res = cli.Get("/numbers/678");
std::cout << res->status << std::endl;
std::cout << res->body << std::endl;
httplib::MultipartFormDataItems items = {
{ "file1", "this is file content", "hello.txt", "text/plain" },
};
auto res = cli.Post("/upload", items);
std::cout << res->status << std::endl;
std::cout << res->body << std::endl;
return 0;
}