自动将本地计算机上指定文件夹中需要备份的文件上传备份到服务器中。并且能够随时通过浏览器进行查看并且下载,其中下载过程支持断点续传功能,而服务器也会对上传文件进行热点管理,将非热点文件进行压缩存储,节省磁盘空间。
这个云备份项目需要我们实现两端程序,其中包括部署在用户机的客户端程序,上传需要备份的文件,以及运行在服务器上的服务端程序,实现备份文件的存储和管理,两端合作实现总体的自动云备份功能。
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]
}
]
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;//转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;
}
GitHub链接
BundleBundle
是一个嵌入式压缩库,支持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 );
}
GitHub链接
httplib
库,一个C++11
单文件头的跨平台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::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);
}
}
不管是客户端还是服务端,文件的传输备份都涉及到文件的读写,包括数据管理信息的持久化也是如此,因此首先设计封装文件操作类,这个类封装完毕之后,则在任意模块中对文件进行操作时都将变的简单化。
C++17中filesystem手册
/*util.hpp*/
class FileUtil{
private:
std::string _name;
public:
FileUtil(const std::string &name);
size_t FileSize();
time_t LastATime();
time_t LastMTime();
std::string FileName();
bool GetPosLen(std::string *content, size_t pos, size_t len);
bool GetContent(std::string *content);
bool SetContent(std::strint *content);
bool Compress(const std::string &packname);
bool UnCompress(const std::string &filename);
bool Exists();
bool CreateDirectory();
bool ScanDirectory(std::vector<std::string> *arry);
};
/*util.hpp*/
class JsonUtil{
public:
static bool Serialize(const Json::Value &root, std::string *str);
static bool UnSerialize(const std::string &str, Json::Value *root);
};
使用文件配置加载一些程序的运行关键信息可以让程序的运行更加灵活。
配置信息:
IP
地址{
"hot_time" : 30,
"server_port" : 8888,
"server_ip" : "172.19.54.175",
"download_prefix" : "/download/",
"packfile_suffix" : ".lz",
"pack_dir" : "./packdir/",
"back_dir" : "./backdir/",
"backup_file" : "./cloud.dat"
}
使用单例模式管理系统配置信息,能够让配置信息的管理控制更加统一灵活。
#define CONFIG_FILE "./cloud.conf"
#ifndef __M_CONFIG_H__
#define __M_CONFIG_H__
#include
#include "util.hpp"
namespace cloud
{
class Config
{
private:
Config(){}
static Config *_instance;
static std::mutex _mutex;
private:
int _hot_time; // 热点判断时间
int _server_port; // 服务器监听端口
std::string _server_ip; // 服务器IP地址
std::string _download_prefix; // 下载url前缀路径
std::string _packfile_suffix; // 压缩包后缀名称
std::string _pack_dir; // 压缩包存放目录
std::string _back_dir; // 备份文件存放目录
std::string _backup_file; // 数据信息存放文件
bool ReadConfigFile(){}
public:
static Config *GetInstance(){}
int GetHotTime(){}
int GetServerPort(){}
std::string GetServerIP(){}
std::string GetDownloadPrefix(){}
std::string GetPackFileSuffix(){}
std::string GetPackDir(){}
std::string GetBackDir(){}
std::string GetBackupFile(){}
};
Config *Config::_instance = nullptr;
std::mutex Config::_mutex;
}
#endif
url
作为请求。json
格式或者自定义方式)/*data.hpp*/
#ifndef __M_DATA_H__
#define __M_DATA_H__
#include
#include
#include "util.hpp"
#include "config.hpp"
namespace cloud
{
typedef struct BackupInfo
{
bool pack_flag; // 是否压缩标志
size_t fsize; // 文件大小
time_t atime; // 最后一次访问时间
time_t mtime; // 最后一次更改时间
std::string real_path; // 文件实际存储路径
std::string pack_path; // 压缩文件储存路径
std::string url_path; //
bool NewBackupInfo(const std::string &realpath){}
} BackupInfo;
class DataManager
{
public:
DataManager(){}
~DataManager(){}
bool Insert(const BackupInfo &info){}
bool Update(const BackupInfo &info){}
bool GetOneByURL(const std::string &url, BackupInfo *info){}
bool GetOneByRealPath(const std::string &realpath, BackupInfo *info){}
bool GetAll(std::vector<BackupInfo> *array){}
bool Storage(){}
bool InitLoad(){}
private:
std::string _backup_file;
pthread_rwlock_t _rwlock; // 读写锁 - 读共享,写互斥
std::unordered_map<std::string, BackupInfo> _table;
};
}
#endif
服务器端的热点文件管理是对上传的非热点文件进行压缩存储,节省磁盘空间。
而热点文件的判断在于上传的文件的最后一次访问时间是否在热点判断时间之内,比如如果一个文件一天都没有被访问过我们就认为这是一个非热点文件,其实就是当前系统时间,与文件最后一次访问时间之间的时间差是否在一天之内的判断。
而我们需要对上传的文件每隔一段时间进行热点检测,相当于遍历上传文件的存储文件夹,找出所有的文件,然后通过对逐个文件进行时间差的判断,来逐个进行热点处理。
基于这个思想,我们需要将上传的文件存储位置与压缩后压缩文件的存储位置分开。这样在遍历上传文件夹的时候不至于将压缩过的文件又进行非热点处理了。
关键点:
#ifndef __M_HOT_H__
#define __M_HOT_H__
#include
#include "data.hpp"
//因为数据管理是要在多个模块中访问的,因此将其作为全局数据定义,在此处声明使用即可
extern cloud::DataManager *_data;
namespace cloud
{
class HotManager
{
public:
HotManager(){}
bool RunModule(){}
private:
// 非热点文件-假 热点文件-真
bool HotJudge(const std::string &filename){}
private:
std::string _back_dir;
std::string _pack_dir;
std::string _pack_suffix;
int _hot_time;
};
}
#endif
云备份项目中 ,业务处理模块是针对客户端的业务请求进行处理,并最终给与响应。而整个过程中包含以下要实现的功能:
业务处理模块要对客户端的请求进行处理,那么我们就需要提前定义好客户端与服务端的通信,明确客户端发送什么样的请求,服务端处理后应该给与什么样的响应,而这就是网络通信接口的设计。
HTTP文件上传:
POST /upload HTTP/1.1
Content-Length:11
Content-Type:multipart/form-data;boundary= ----WebKitFormBoundary+16字节随机字符
------WebKitFormBoundary
Content-Disposition:form-data;filename="a.txt";
hello world
------WebKitFormBoundary--
HTTP/1.1 200 OK
Content-Length: 0
HTTP文件列表获取:
GET /list HTTP/1.1
Content-Length: 0
HTTP/1.1 200 OK
Content-Length:
Content-Type: text/html
Page of Download
Download
a.txt
1994-07-08 03:00
27K
HTTP文件下载:
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
文件数据
HTTP断点续传:
GET /download/a.txt http/1.1
Content-Length: 0
If-Range: "文件唯一标识"
Range: bytes=89-999
HTTP/1.1 206 Partial Content
Content-Length:
Content-Range: bytes 89-999/100000
Content-Type: application/octet-stream
ETag: "inode-size-mtime一个能够唯一标识文件的数据"
Accept-Ranges: bytes
对应文件从89到999字节的数据。
//因为业务处理的回调函数没有传入参数的地方,因此无法直接访问外部的数据管理模块数据
//可以使用lamda表达式解决,但是所有的业务功能都要在一个函数内实现,于功能划分上模块不够清晰
//因此将数据管理模块的对象定义为全局数据,在这里声明一下,就可以在任意位置访问了
#ifndef __M_SERVICE_H__
#define __M_SERVICE_H__
#include
#include "data.hpp"
#include "httplib.h"
extern cloud::DataManager *_data;
namespace cloud
{
class Service
{
public:
Service(){}
bool RunModule(){}
private:
static void Upload(const httplib::Request &req, httplib::Response &rsp){}
// 静态成员函数不能调用非静态成员函数 -- 没有this指针
static std::string TimetoStr(time_t t){}
static void ListShow(const httplib::Request &req, httplib::Response &rsp){}
static std::string GetETag(const BackupInfo &info){}
static void Download(const httplib::Request &req, httplib::Response &rsp){}
private:
std::string _server_ip; // IP地址
int _server_port; // 端口号
std::string _download_prefix; // 下载前缀
httplib::Server _server; // http对象
};
}
#endif
客户端要实现的功能是对指定文件夹中的文件自动进行备份上传。但是并不是所有的文件每次都需要上传,我们需要能够判断,哪些文件需要上传,哪些不需要,因此需要将备份的文件信息给管理起来,作为下一次文件是否需要备份的判断。因此需要被管理的信息包含以下:
这个其实与服务端的文件实用工具类雷同,只是功能需求并没有服务端那么多。
#ifndef __M_SERVICE_H__
#define __M_SERVICE_H__
#include
#include "data.hpp"
#include "httplib.h"
extern cloud::DataManager *_data;
namespace cloud
{
class Service
{
public:
Service(){}
bool RunModule(){}
private:
static void Upload(const httplib::Request &req, httplib::Response &rsp){}
// 静态成员函数不能调用非静态成员函数 -- 没有this指针
static std::string TimetoStr(time_t t){}
static void ListShow(const httplib::Request &req, httplib::Response &rsp){}
static std::string GetETag(const BackupInfo &info){}
static void Download(const httplib::Request &req, httplib::Response &rsp){}
private:
std::string _server_ip; // IP地址
int _server_port; // 端口号
std::string _download_prefix; // 下载前缀
httplib::Server _server; // http对象
};
}
#endif
#ifndef __M_SERVICE_H__
#define __M_SERVICE_H__
#include
#include "data.hpp"
#include "httplib.h"
extern cloud::DataManager *_data;
namespace cloud
{
class Service
{
public:
Service(){}
bool RunModule(){}
private:
static void Upload(const httplib::Request &req, httplib::Response &rsp){}
// 静态成员函数不能调用非静态成员函数 -- 没有this指针
static std::string TimetoStr(time_t t)
static void ListShow(const httplib::Request &req, httplib::Response &rsp){}
static std::string GetETag(const BackupInfo &info){}
static void Download(const httplib::Request &req, httplib::Response &rsp){}
private:
std::string _server_ip; // IP地址
int _server_port; // 端口号
std::string _download_prefix; // 下载前缀
httplib::Server _server; // http对象
};
}
#endif
#ifndef __M_SERVICE_H__
#define __M_SERVICE_H__
#include
#include "data.hpp"
#include "httplib.h"
extern cloud::DataManager *_data;
namespace cloud
{
class Service
{
public:
Service(){}
bool RunModule(){}
private:
static void Upload(const httplib::Request &req, httplib::Response &rsp){}
// 静态成员函数不能调用非静态成员函数 -- 没有this指针
static std::string TimetoStr(time_t t)
static void ListShow(const httplib::Request &req, httplib::Response &rsp){}
static std::string GetETag(const BackupInfo &info){}
static void Download(const httplib::Request &req, httplib::Response &rsp){}
private:
std::string _server_ip; // IP地址
int _server_port; // 端口号
std::string _download_prefix; // 下载前缀
httplib::Server _server; // http对象
};
}
#endif
项目名称:云备份系统
项目功能:搭建云备份服务器与客户端,客户端程序运行在客户机上自动将指定目录下的文件备份到服务器,并且能够支持浏览器查看与下载,其中下载支持断点续传功能,并且服务器端对备份的文件进行热点管理,将长时间无访问文件进行压缩存储。
开发环境:centos7.6/vim、g++、gdb、makefile
以及windows11/vs2022
技术特点:http
客户端/服务器搭建,json
序列化,文件压缩,热点管理,断点续传,线程池,读写锁,单例模式
项目模块:
http
服务器与客户端进行通信处理客户端的上传,下载,查看请求,并支持断点续传c++17
文件系统库,遍历获取指定文件夹下所有文件。http
客户端上传备份文件。