云备份项目
1. 项目介绍
云备份项目的大体流程是将客户端的监控目录下的文件,自动上传到服务器上进行备份。并且用户能够通过浏览器查看和下载自己的备份文件。
其中客户端支持调用系统接口监控目录。服务端下载过程支持断点续传,对备份文件进行热点管理,将非热点文件压缩存储,节省磁盘空间。
1.1 服务端功能细分
- 支持客户端文件的上传功能
- 支持备份文件的浏览器查看、搜索和下载功能
- 对备份文件的统计管理
- 对热点文件的压缩处理(对长时间无访问的文件进行压缩存储)
1.2 服务端模块划分
模块 | 解释 |
---|---|
配置管理模块 | 系统运行加载配置信息的单例类 |
持久化器模块 | 负责数据的持久化包括加载和写入,支持文件和数据库方向,并支持方向扩展 |
数据管理模块 | 统计文件的相关信息并管理文件,以便随时使用,解耦数据和业务 |
异步任务模块 | 设计生产消费模型,使用线程池异步处理自定义任务,如压缩任务 |
热点管理模块 | 统计文件的热点,区分热点与非热点文件的管理方式 |
网络通信模块 | 实现客户端服务器的网络通信 |
业务处理模块 | 文件上传,网页查看,文件下载(断点续传) |
配置管理模块
单例配置类是大多数的项目中都会使用的一个类,用于管理系统运行时需要加载的所有配置信息。本模块为配置管理模块,实现了一个单例类config,用于在整个应用程序生命周期中管理和提供配置信息。
同时,通过封装JSON处理细节,简化了配置信息的加载逻辑,使代码更加清晰和易于理解。
本次云备份项目需要保存的配置信息,一般有热点时间、上传请求前缀、获取记录的请求前缀、下载请求的前缀、查看文件列表的请求前缀、备份和压缩地址路径的前缀、持久化记录文件的路径、服务器的IP地址和端口号以及MySQL用户名密码IP端口数据库名等。
class config {
public:
static config& instance();
public:
size_t hot_time();
const std::string& upload_prefix();
const std::string& record_prefix();
const std::string& download_prefix();
const std::string& filelist_prefix();
const std::string& back_dir();
const std::string& pack_dir();
const std::string& record_file();
uint16_t server_port();
const std::string& mysql_user();
const std::string& mysql_passwd();
const std::string& mysql_host();
uint16_t mysql_port();
const std::string& mysql_db();
const std::string& persist_type();
private:
config();
config(const config&) = delete;
config& operator=(const config&) = delete;
private:
size_t _hot_time;
std::string _upload_prefix;
std::string _record_prefix;
std::string _download_prefix;
std::string _filelist_prefix;
std::string _back_dir;
std::string _pack_dir;
std::string _record_file;
uint16_t _server_port;
std::string _mysql_user;
std::string _mysql_passwd;
std::string _mysql_host;
uint16_t _mysql_port;
std::string _mysql_db;
std::string _persist_type;
};
持久化器模块
本模块是面向现代C++的数据持久化框架设计,利用了C++的多态、模板编程、智能指针和设计模式等高级特性。旨在支持多种形式的数据持久化策略,如文件存储和数据库存储,特别适用于备份信息的存储和加载。
persist类是抽象基类,定义持久化的store和load两个纯虚方法,分别用于存储和加载backup_info类型的数据。还定义自身的智能指针类型ptr,方便外部通过智能指针管理persist子类的实例。为文件存储和数据库存储提供了统一接口。
file_persist类继承自persist,实现了基于文件的数据持久化策略。store方法通过将备份信息对象序列化为JSON 并写入文件来实现持久化。load方法则从该文件读取数据,反序列化JSON数据,最终返回备份信息对象的数组。
sql_persist类同样继承自persist,实现了基于数据库的数据持久化策略。它在构造函数中获取数据库的配置信息。其store方法将backup_info对象转换成元组并执行SQL替换操作来存储数据。load方法通过执行SQL选择操作并将结果集转换回backup_info对象的数组并返回。
persist_factory是一个工厂结构,提供了静态方法create来简化对象的创建过程。该方法根据传入的persist_type和args来动态创建具体类型的持久化对象。此设计模式使得持久化策略的扩展变得简单,还利于维持代码的解耦和灵活性。
class persist {
public:
using ptr = std::shared_ptr<persist>;
using infos = std::vector<backup_info>;
public:
virtual void store(const infos&) = 0;
virtual infos load() = 0;
};
class file_persist : public persist {
public:
virtual void store(const infos& infov);
virtual infos load();
private:
util::file _record;
};
class sql_persist : public persist {
public:
virtual void store(const infos& infov);
virtual infos load();
};
struct persist_factory {
template<class persist_type, class... Args>
static persist::ptr create(Args&&... args);
};
数据管理模块
本模块提供综合性的数据管理类,负责备份信息的存储与管理。对外提供了简明扼要且易于使用的接口。
backup_info类负责封装和管理备份信息的具体细节,包括文件路径、备份路径、打包路径和下载URL等关键数据。该结构还提供了从JSON和元组中构造对象、以及将对象序列化为JSON或元组的能力,展示了数据封装与信息隐藏的良好实践。
数据管理单例类data_manager采用单例模式,使用shared_ptr和shared_mutex确保程序对备份信息的访问都是线程安全的。该类通过哈希表维护了一个内存级的数据库,并对外提供这些备份信息的更新插入查询和删除操作。
通过信号处理和持久化操作的结合,实现了程序的优雅退出和数据的安全存储。
struct backup_info {
using tuple_of_info = std::tuple<bool, size_t, time_t, time_t, time_t, \
std::string, std::string, std::string, std::string>;
bool ishot;
size_t fsize;
time_t atime;
time_t mtime;
time_t ctime;
std::string fpath;
std::string bpath;
std::string ppath;
std::string dlurl;
backup_info() = default;
backup_info(const std::string& file_path);
backup_info(const util::json& js);
backup_info(const tuple_of_info& tp);
util::json parser_to_json();
tuple_of_info parser_to_tuple();
void parserfrom(const util::json& js);
void parserfrom(const tuple_of_info& tp);
};
class data_manager {
public:
static data_manager& instance();
void update(const backup_info& info);
void insert(const backup_info& info);
backup_info& get_by_fpath(const std::string& fpath);
backup_info& get_by_url(const std::string& url)
backup_info& get_by_bpath(const std::string& bpath)
backup_info& get_by_ppath(const std::string& ppath)
std::vector<backup_info> get_all();
void delete_by_fpath(const std::string& fpath);
private:
void handle_error(const std::string& fpath);
data_manager();
~data_manager();
data_manager(const data_manager&) = delete;
data_manager& operator=(const data_manager&) = delete;
private:
void load();
void store();
private:
static void signal_handler(int signo);
private:
std::unordered_map<std::string, backup_info> _url2info;
std::shared_mutex _mtx; // rw lock
persist::ptr _persister;
};
异步任务模块
该模块实现基于C++的并发编程模型(下文简称cp_model),通过消费者-生产者模式来进行任务的分发与执行。该模型采用现代C++特性,包括智能指针、模板、lambda表达式和多线程编程,创建了一个灵活、高效且可扩展的任务执行框架。
通过抽象基类task和派生类pack_task、cb_task的实现。每个task对象能够包装一个特定的操作,通过覆写 operator()来实现具体的任务逻辑。这种设计使得任务对象能够以统一的接口被调度和执行,同时保持操作逻辑的封装与隔离。
灵活的任务创建task_factory结构体通过工厂模式提供了一个灵活的任务创建机制。通过create方法,可以便捷地构造task对象,自动管理其生命周期。
cp_model类使用了一个线程池和一个循环队列来实现任务的调度与执行。线程池中的线程负责从队列中取出任务并执行,这种设计能够平衡任务分发的负载,有效利用多核资源。
循环队列利用信号量控制任务队列的访问,以及通过互斥锁保护push和pop接口的线程安全,避免了生产者和消费者之间的资源竞争。
class task {
public:
using ptr = std::shared_ptr<task>;
task() = default;
virtual void operator()();
};
class pack_task : public task {
public:
pack_task() = default;
pack_task(backup_info* info);
virtual void operator()() override;
private:
backup_info* _info;
};
class cb_task : public task {
public:
using handle = std::function<void()>;
public:
cb_task() = default;
cb_task(const handle& cb);
virtual void operator()() override;
handle _cb;
};
struct task_factory {
template<typename task_type, typename... Args>
static task::ptr create(Args&&... args);
};
template<class value>
class loop_queue {
public:
loop_queue(size_t size = config::instance().queue_size());
void push(value v);
void pop(value& v);
private:
std::vector<value> _q;
std::mutex _p_mtx;
std::mutex _c_mtx;
util::semaphore _p_sem;
util::semaphore _c_sem;
int _p_step;
int _c_step;
};
class cp_model {
private:
using task_ptr = std::shared_ptr<task>;
public:
static cp_model& instance(size_t size = config::instance().thread_num());
void push(task_ptr t);
private:
cp_model(size_t size);
void routine();
cp_model(const cp_model&) = delete;
cp_model& operator=(const cp_model&) = delete;
private:
std::vector<std::thread> _thpool;
loop_queue<task_ptr> _queue;
};
热点管理模块
hot_manager类采用单例模式设计,负责检测并处理热点文件的。热点文件指那些在设定的时间窗口内频繁访问或修改的文件,当文件不再活跃时,它们会被打包压缩以节省存储空间。
hot_manager的核心功能是通过run方法启动一个分离的线程,该线程周期性地执行detect方法。遍历back_dir中的所有文件,检测每个文件的状态,并对那些符合特定条件的非热点文件,创建pack_task任务并交给到 cp_model异步执行。这意味着文件打包任务可以在程序的其他操作同时进行,提高了整体效率。
class hot_manager {
public:
hot_manager();
void run();
private:
bool detect();
private:
size_t _hot_time;
std::string _back_dir;
std::string _pack_dir;
data_manager* _data_mgr;
thread_pool<task>* _thread_pool;
};
业务处理模块
本模块实现基于HTTP协议的Web服务器,该服务器用于处理与备份文件相关的各种请求,例如搜索、记录查询、文件上传、文件下载以及文件列表页面的提供。
business类为Web服务器提供不同业务的方法实现。包括:
- search:基于URL中的文件名进行搜索,返回文件信息的JSON格式。
- record:返回所有文件的信息。
- upload:处理文件上传请求,保存文件并更新数据。
- download:处理文件下载请求,提供文件的内容作为响应。
- filelist:返回文件列表页面。
- webserver:用于服务Web根目录下的静态页面和资源。
将业务逻辑封装在静态方法中,保持了代码的模块化和解耦,同时简化了业务逻辑与HTTP处理逻辑之间的接口。
server类使用单例模式,保证整个应用中只存在一个服务器实例。它负责配置和启动HTTP服务器,包括注册业务处理函数并开始监听HTTP请求。
响应特定URL的请求通过regist方法中的回调进行配置。httplib::Server的设计允许服务器高效处理并发请求,利用多线程优化响应时间和服务能力。
struct business {
static void record(const httplib::Request& req, httplib::Response& rsp);
static void upload(const httplib::Request& req, httplib::Response& rsp);
static void download(const httplib::Request& req, httplib::Response& rsp);
static void filelist(const httplib::Request& req, httplib::Response& rsp);
};
class server {
public:
using business = std::function<void(const httplib::Request&, httplib::Response&)>;
public:
server(business record, business upload, business download, business filelist);
void start();
private:
std::string _ip;
uint16_t _port;
httplib::Server _svr;
business _record;
business _upload;
business _download;
business _filelist;
};
断点续传
断点续传就是如果发生异常断开下载,再次下载会从上次断开的位置继续下载。
实现思想是客户端下载时记录当前的数据量,如发生中断,下次下载向服务器通告下载区间,服务器仅回传区间数据。
断点续传的请求响应格式。
GET /download/a.txt http/1.1
If-Range: "文件唯一标识"
Range: bytes=89-999
-------------------------------------------
HTTP/1.1 206 Partial Content
Accept-Ranges: bytes
Content-Range: bytes 89-999/100000
Content-Type: application/octet-stream
ETag: "文件唯一标识"
对应文件从89到999字节的数据。
1.3 客户端功能细分
- 支持检测指定目录中的文件
- 支持判断备份文件的状态(新增文件、修改文件、空闲文件)
- 支持上传文件到服务器
1.4 客户端模块划分
模块 | 解释 |
---|---|
配置管理模块 | 系统运行加载配置信息的单例类 |
数据管理模块 | 统计文件的相关信息并管理文件 |
持久化器模块 | 负责数据的持久化包括加载和写入,支持文件方向 |
业务处理模块 | 自动上传备份文件到服务器 |
目录监控模块 | 使用Windows/MacOS/Linux的系统级API,监控目录文件的变动 |
文件检测模块 | 监控备份目录下的文件,判断文件状态 |
数据管理模块
data_manager类是管理客户端数据的核心角色。它主要负责备份信息的管理,包括更新、插入、查询以及删除等操作。
利用instance方法实现单例,保证全局唯一,方便在应用的任何地方访问和操作备份信息对象。update方法通过给定的backup_info对象更新备份信息。如果信息不存在则插入新条目,存在则更新。insert方法直接复用update方法。
get_by_fpath和 get_by_bpath方法提供了通过文件路径或备份路径查询备份信息的能力。get_all方法返回当前所有备份信息。 delete_by_fpath和delete_by_bpath方法允许用户通过指定的文件路径或备份路径来删除对应的备份信息。
构造函数中注册的信号处理器signal_handler将在接收到信号时触发,调用store方法持久化当前的备份信息至存储介质,随后通过killer::kill_self()安全终止程序。信号处理的实现增加了程序的健壮性,确保了应用能够在接收到终止信号时安全地存储数据。
通过在各个方法内部使用std::mutex加锁,保障了数据操作的线程安全,这在多线程环境下尤其关键。
确保了数据管理逻辑集中且易于访问,适用于备份信息这类全局唯一的资源管理。通过互斥锁有效防止了数据竞争和条件竞争,保障了数据的一致性和稳定性。
struct backup_info {
time_t mtime;
size_t fsize;
std::string fpath;
std::string bpath;
backup_info() = default;
backup_info(const util::json& js);
backup_info(const std::string& file_path);
time_t modify_time();
std::string content();
util::json parser_to_json() const;
void parserfrom(const util::json& js);
};
class data_manager {
public:
static data_manager* get_instance();
private:
static void signal_handler(int signo);
data_manager();
~data_manager();
public:
bool load();
bool storage();
bool insert(const backup_info& info, bool willstorage = true);
bool update(const backup_info& info);
bool del_info_by_name(const std::string& url);
bool get_info_by_name(const std::string& fname, backup_info*& pinfo);
bool get_info_by_path(const std::string& rpath, backup_info*& pinfo);
bool get_infos_all(std::vector<backup_info>* infos);
void handle_error(const std::string& rpath);
std::unordered_map<std::string, backup_info>& get_info_map();
private:
std::unordered_map<std::string, backup_info> _url2info;
std::string _storage_file;
std::shared_mutex _rwlock;
static data_manager* _inst;
static std::mutex _mtx;
};
目录监控模块
本模块是通用的系统目录监视器,根据不同操作系统实现细节的派生类watcher。这些类用于监视文件系统中特定目录下的变化,包括文件的创建、修改和删除,并对这些事件进行响应。
base_watcher是监视器共享功能的基类,提供了注册回调函数的机制,这些回调函数用于处理文件的创建、修改和删除事件。使用了std::function作为回调类型,允许使用函数、lambda表达式或其他可调用对象,增强灵活性。使用check_cb函数来确保回调函数在监视前都已注册。
MacOS使用FSEvents API来监听文件系统事件。Linux使用inotify API来监听文件系统事件。Windows使用ReadDirectoryChangesW API检测文件系统的变化。
通过针对不同操作系统提供不同实现,展示了良好的可移植性和多平台支持。将文件监视功能封装在类中,并通过回调机制与业务逻辑解耦,使得代码易于扩展和维护。
class watcher {
public:
using callback_t = std::function<void(const std::string&)>;
static watcher& instance();
void regist(callback_t add_cb, callback_t mod_cb, callback_t del_cb);
private:
watcher() = default;
watcher(const watcher&) = delete;
watcher& operator=(const watcher&) = delete;
void check_cb();
#if defined (__APPLE__)
public:
~watcher();
void watch(std::string dir);
private:
static void callback(ConstFSEventStreamRef, void*, size_t num, void* epaths, \
const FSEventStreamEventFlags* eflags, const FSEventStreamEventId*);
private:
FSEventStreamRef _sr;
#elif defined (__linux__)
public:
void watch(std::string dir);
private:
void loop_once(int hd);
#elif defined (_WIN32) || defined (_WIN64)
public:
void watch(std::string dir);
private:
void loop_once(WCHAR* wcdir, HANDLE hdlr);
private:
std::string wchar2str(const LPCTSTR& widestr);
bool is_directory(const LPTSTR& path);
bool is_file_opened(const std::wstring& path);
#endif
private:
callback_t _create_handler;
callback_t _modify_handler;
callback_t _delete_handler;
};
文件检测模块
detector类是一个综合文件监控的组件。该系统主要负责监控指定目录下的文件变化(创建、修改、删除),并根据这些变化触发相应的数据管理和网络通信操作。
detector类使用了多个组件,包括文件目录监控watcher、数据管理data_manager以及网络客户端操作client。体现了面向对象编程的模块化和职责分离原则。
run方法首先执行一次目录的全扫描,处理启动时已存在但可能已更新的文件。在处理创建事件时,通过启动独立的线程进行文件的异步上传,避免阻塞主监控循环。
然后启动watcher实例对目录进行实时监控。通过回调函数handler_create、handler_modify和handler_delete处理文件系统中的相应事件。这些函数中包含了文件信息的录入、更新以及对应文件的上传或删除操作,以保持本地和远程数据的同步。
在修改事件处理中,通过比较文件最后修改时间和记录的修改时间(加上配置的时间间隔)来决定是否上传更新,减少不必要的上传操作。
class detector {
public:
detector();
void run();
public:
void handler_create(const std::string& bpath);
void handler_modify(const std::string& bpath);
void handler_delete(const std::string& bpath);
private:
std::string _back_dir;
size_t _gap_time;
data_manager& _data_mgr;
client& _http_cli;
watcher& _watcher;
};
业务处理模块
client的类提供作为客户端与服务器进行通信的接口。它使用了单例模式确保全局唯一的实例,并利用第三方库httplib的Client类来实现HTTP请求的发送。
提供upload_by_bpath和upload_by_fpath方法用于上传文件。这两个方法接收文件的备份路径或文件路径作为输入,检索相应的backup_info来获取文件的内容,并构建 MultipartFormData来发送POST请求。delete_by_fpath和delete_by_bpath方法预期用于执行删除操作。
class client {
public:
static client& instance();
bool upload_by_bpath(const std::string& bpath);
bool upload_by_fpath(const std::string& fpath);
bool delete_by_fpath(const std::string& fpath);
bool delete_by_bpath(const std::string& bpath);
private:
client(const std::string& sip, uint16_t sport);
httplib::Client _cli;
data_manager& _data_mgr;
};
程序入口模块
entry类作为应用程序的入口点,负责启动整体的文件监控与管理流程。该类通过包含detector组件并提供特定的方法来实现应用的初始化、日志配置和守护进程化等功能。
守护进程化daemonize方法分别针对 Linux/Unix系统和Windows系统提供了守护进程或服务化的逻辑。对于 Linux/Unix,通过 fork创建子进程,并在子进程中通过setsid创建新会话,随后改变文件掩码,关闭标准输入输出文件描述符实现守护进程化。
对于Windows,使用CreateProcess创建隐藏窗口的子进程,实现类似守护进程的功能。提供set_logger静态方法来配置日志系统,使用easylog库来初始化异步日志记录器,包括设置日志文件、日志等级等,以便于应用的日志管理。
class entry {
public:
entry();
void run();
#if defined (__linux__) || defined (__APPLE__)
static void daemonize();
#elif defined (_WIN32) || defined (_WIN64)
static void daemonize();
~entry();
private:
STARTUPINFO _si;
PROCESS_INFORMATION _pi;
#endif
public:
static void set_logger();
private:
detector _detector;
};