前言
依旧是老话题,在开始看前,对整个项目不熟悉的老铁可以看看小编先前的博客:云备份项目介绍,看完之后再回过来当前内容会好很多。
下面不再多说,接下来直接开始进入正题:
热点模块
- 热点管理模块功能:对服务器上备份的文件进行检测,判断哪些文件长时间没有被访问,长时间没有被访问的文件就是非热点文件
对于非热点文件的处理,就是将原文件进行压缩存储,节省磁盘空间。主要的实现思路如下:
- 遍历备份目录,获取所有的文件路径名
- 逐个遍历每个文件,获取到文件的最后一次访问时间与当前系统时间进行比较
- 对非热点文件进行压缩处理,删除源文件
- 修改数据管理模块对应的文件信息,将文件的压缩标志位修改为:
true
热点管理类
RunModule
:对非热点文件进行管理HotJudge
:热点文件判断
在 src 目录下创建 hot.hpp
文件,设计HotManager
类:
#ifndef __MY_HOT__
#define __MY_HOT__
#include "data.hpp"
#include <unistd.h>
extern cloud::DataManager *_data; //用作全局
namespace cloud
{
// 热点管理类
class HotManager
{
public:
HotManager()
{
// 从配置文件获取信息
Config *config = Config::GetInstance();
_pack_dir = config->GetPackDir();
_pack_suffix = config->GetPackFileSuffix();
_back_dir = config->GetBackDir();
_hot_time = config->GetHotTime();
// 目录不存在直接创建
FileUtil tmp1(_pack_dir);
FileUtil tmp2(_back_dir);
tmp1.CreateDirectory();
tmp2.CreateDirectory();
}
bool RunModule()
{
//实时监测热点文件
while (1)
{
// 1.遍历备份目录,获取所有的文件路径名
FileUtil fu(_back_dir);
std::vector<std::string> array;
// 将获取的文件名存放到array
fu.ScanDirectory(&array);
// 2.逐个判断是否为热点文件
for (auto &e : array)
{
if (HotJudge(e) == false)
{
// 热点文件不做处理
continue;
}
// 3.获取文件的备份信息
BackupInfo bi;
// 防止有备份文件的信息没有录入
if (_data->GetOneByRealPath(e, &bi) == false)
{
// 将当前文件信息进行重新设置
bi.NewBackupInfo(e);
}
// 4.对非热点文件进行压缩处理
FileUtil tmp(e);
tmp.Compress(bi.pack_path); // 压缩+压缩后缀名
// 5.删除源文件,修改备份信息
tmp.Remove();
_data->UpDate(bi); // 更新数据
}
//休眠,减少CUP运行
usleep(1000);
}
}
private:
// 非热点文件判断:非热点true,热点false
bool HotJudge(std::string &filename)
{
FileUtil fu(filename);
// 文件最后访问时间
time_t last_time = fu.LastATime();
time_t now_time = time(NULL);
// 当前系统时间 - 最后访问时间
if (now_time - last_time > _hot_time)
return true;
return false;
}
private:
std::string _pack_dir;
std::string _pack_suffix; // 压缩包后缀名
std::string _back_dir;
size_t _hot_time; // 热点时间
};
}
#endif
业务处理模块
由于云备份项目借用了 httplib 第三方库,网络通信直接通过httplib库完成。将网络通信模块和业务处理进行了合并,这里只需要着重关注如何处理业务处理即可。
业务请求处理包含以下几点:
- 文件上传请求:备份客户端上传的文件,响应上传成功
- 文件列表请求:客户端浏览器请求一个备份文件的展示页面,响应页面
- 文件下载请求:通过展示页面,用户点击下载,响应客户端要下载的文件数据
业务处理类
RunModule
:业务处理函数UpLoad
:文件上传 请求处理函数ListShow
:文件总页面展示 请求处理函数DownLoad
:文件下载 请求处理函数
在 scr 目录下创建 server.hpp
文件,编写业务处理类代码:
#ifndef __My_SERVER__
#define __My_SERVER__
#include "data.hpp"
#include "httplib.h"
extern cloud::DataManager* _data;
namespace cloud
{
class server
{
public:
server()
{
//使用配置文件进行初始化处理
Config* config = Config::GetInstance();
_server_port = config->GetServerPort();
_server_ip = config->GetServerIP();
_download_prefix = config->GetDownloadPrefix();
}
//业务处理函数
bool RunModule()
{
//文件上传请求
_server.Post("/upload", Upload);
//文件总页面展示请求
_server.Get("/listshow", ListShow);
_server.Get("/", ListShow); //默认请求
//文件下载请求
std::string download_url = _download_prefix + "(.*)";
//(.*)是正则表达式;. 点表示任意字符,*表示前面的字符可以出现任意次数,一次或者多次
_server.Get(download_url.c_str(), Download);
//捕获客户端连接
_server.listen(_server_ip.c_str(), _server_port);
return true;
}
private:
//文件上传请求处理函数
static void Upload(const httplib::Request &req, httplib::Response &rsp)
{
//整个请求报文的正文不全为文件数据
auto ret = req.has_file("file");
if(ret == false) //判断有没有上传文件区域
{
rsp.status = 400;
return ;
}
const auto& file = req.get_file_value("file"); //获取请求中的文件数据
//file.filename文件名 file.content文件内容
//备份文件所在的目录
std::string back_dir = Config::GetInstance()->GetBackDir();
//文件路径 == 备份文件所在的目录+文件名
std::string realpath = back_dir + FileUtil(file.filename).FileName();
FileUtil fu(realpath);
//将上传文件的内容写入到realpath中
fu.SetContent(file.content);
//数据备份好,将数据管理起来
BackupInfo info;
info.NewBackupInfo(realpath); //组织备份文件的信息
_data->Insert(info);//向数据管理模块插入新的备份文件信息
}
//时间戳转特定的时间格式
static std::string TimeToStr(const time_t t)
{
std::string tmp = std::ctime(&t);
return tmp;
}
//文件总页面展示请求处理函数
static void ListShow(const httplib::Request &req, httplib::Response &rsp)
{
//1.获取所有的备份文件信息
std::vector<BackupInfo> array;
_data->GetAll(&array);
//2.根据备份文件的信息,组织html文件格式
std::stringstream ss;
ss << "<!DOCTYPE html><html lang='en'><head><meta charset='UTF-8'>";
ss << "<meta name='viewport' content='width=device-width, initial-scale=1.0'>";
ss << "<title>文件下载页面</title><style>";
ss << "body {font-family: Arial, sans-serif;margin: 40px;}";
ss << ".download-table{width: 100%;border-collapse: collapse; margin-top: 20px;}";
ss << ".download-table th, .download-table td {border: 1px solid #ddd;padding: 8px;text-align: left;}";
ss << ".download-table th { background-color: #f2f2f2;}";
ss << ".download-table .date, .download-table .size {text-align: right;}";
ss << ".download-link {text-decoration: none;color: #007bff; }";
ss << ".download-link:hover { text-decoration: underline; }";
ss << " </style> </head><body><h1>文件下载</h1>";
ss << "<p>请从下面的列表中选择要下载的文件。</p> <table class='download-table'>";
ss << "<thead><tr><th>文件名</th><th class='date'>日期</th> <th class='size'>大小</th></tr></thead><tbody>";
for(auto& a : array)
{
ss << "<tr><td>";
std::string filename = FileUtil(a.real_path).FileName();
ss << "<a href='" << a.url << "'class='download-link'>" << filename << "</a>";
ss << "</td><td class='date'>" << TimeToStr(a.mtime) << "</td>"; //显示最后修改时间
ss << "<td class='size'>" << a.fsize / 1024 << "k</td></tr>"; //文件大小以k显示
}
ss << "</tbody></table></body></html>";
//将刚刚编好的html格式回应给客户端
rsp.body = ss.str();
//设置头部内容为html文本
rsp.set_header("Content-Type", "text/html");
rsp.status = 200;
}
//获取唯一标识符:文件名+文件大小+文件最后一次修改时间
static std::string GetETag(const BackupInfo& info)
{
FileUtil fu(info.real_path);
std::string etag = fu.FileName();
etag += '-';
etag += std::to_string(info.fsize);
etag += '-';
etag += std::to_string(info.mtime); //最后一次修改时间
return etag;
}
//文件下载请求处理函数
static void Download(const httplib::Request &req, httplib::Response &rsp)
{
/1.获取客户端请求的资源路径path req.path
//2.根据资源路径,获取文件备份信息
BackupInfo info;
_data->GetOneByUrl(req.path, &info); //通过资源路径获取备份文件的信息
//3.文件是否被压缩?是,先进行解压;删除对应的压缩包,修改文件数据信息
if(info.pack_flag == true)
{
FileUtil fu(info.pack_path); //压缩文件所在位置
//将文件解压到备份文件路径下
fu.UnCompress(info.real_path);
fu.Remove();
//更新数据信息
info.pack_flag = false;
_data->UpDate(info);
}
//4.读取文件信息,将内容设置到rsp中
FileUtil fu(info.real_path); //备份文件
fu.GetContent(&rsp.body);
//5.设置响应报头字段:ETag、Accept-Ranges:bytes;
rsp.set_header("Accept-Ranges", "bytes");//通过特定的区间来访问资源
rsp.set_header("ETag", GetETag(info)); //用于确定文件是否被修改
rsp.set_header("Content-Type", "application/octet-stream"); //响应的内容被视为任意的二进制数据
rsp.status = 200;
}
private:
int16_t _server_port;
std::string _server_ip;
std::string _download_prefix; // 文件下载资源路径
httplib::Server _server;
};
}
#endif
断点续传功能
当文件下载过程中,因为某种异常而中断,如果再次进行从头下载,效率较低。为了提高效率,实现断点续传功能。
- 功能介绍:断点续传就是从上次下载断开的位置重新下载,之前已经传输过的数据将不需要在重新传输
客户端在下载文件的时候,要每次接收到数据写入文件后记录自己当前下载的数据量。当异常下载中断时,下次断点续传的时候,将要重新下载的数据区间(下载起始位置,结束位置)发送给服务器。服务器收到后,仅仅回传客户端需要的区间数据即可。
实现断点续传需要考虑这样的一个问题:如果上次下载文件之后,这个文件在服务器上被修改了,则这时候将不能重新断点续传,而是应该重新进行文件下载操作。
改写文件下载请求处理函数,增加断点续传功能:
// 文件下载请求处理函数
static void Download(const httplib::Request &req, httplib::Response &rsp)
{
// 1.获取客户端请求的资源路径path req.path
// 2.根据资源路径,获取文件备份信息
BackupInfo info;
_data->GetOneByUrl(req.path, &info); // 通过资源路径获取备份文件的信息
// 3.文件是否被压缩?是,先进行解压;删除对应的压缩包,修改文件数据信息
if (info.pack_flag == true)
{
FileUtil fu(info.pack_path); // 压缩文件所在位置
// 将文件解压到备份文件路径下
fu.UnCompress(info.real_path);
fu.Remove();
// 更新数据信息
info.pack_flag = false;
_data->UpDate(info);
}
// 4.读取文件信息,将内容设置到rsp中
FileUtil fu(info.real_path); // 备份文件
fu.GetContent(&rsp.body);
bool retrans = false; // 用于标识是否符合断点续传
std::string old_etag;
if (req.has_header("If-Range"))
{
old_etag = req.get_header_value("If-Range");
// 含有If-Range字段,If-Range字段的etag值与请求的文件最新的etag一致,则符合断点续传
if (old_etag == GetETag(info))
retrans = true;
}
// 5.设置响应报头字段:ETag、Accept-Ranges:bytes;
if (retrans == false)
{
// 不符合断点续传,直接传整个备份文件
fu.GetContent(&rsp.body);
rsp.set_header("Accept-Ranges", "bytes"); // 通过特定的区间来访问资源
rsp.set_header("ETag", GetETag(info)); // 用于确定文件是否被修改
rsp.set_header("Content-Type", "application/octet-stream"); // 响应的内容被视为任意的二进制数据
rsp.status = 200;
}
else // 符合断点续传
{
//httplib库内部实现了断点续传的功能
//在这里只需要将文件的内容读取到body,httplib内部函数会自动处理对应区间数据
//从body中取出指定区间数据进行响应
fu.GetContent(&rsp.body);
rsp.set_header("Accept-Ranges", "bytes"); // 通过特定的区间来访问资源
rsp.set_header("ETag", GetETag(info));
rsp.status = 206;
}
}
服务端整体功能联调
为了能够联动服务端各个模块的功能,下面采取多线程的方式分别执行热点模块、业务处理模块:
#include <thread>
#include "hot.hpp"
#include "server.hpp"
cloud::DataManager* _data;
void HotTask()
{
cloud::HotManager hot;
//进行热点文件检测
hot.RunModule();
}
void ServerTask()
{
cloud::server srv;
//启动服务器进行业务处理
srv.RunModule();
}
int main(int argc, char *argv[])
{
_data = new cloud::DataManager();
//热点模块任务
std::thread thread_Hot(HotTask);
//业务处理模块
std::thread thread_Server(ServerTask);
//等待线程
thread_Hot.join();
thread_Server.join();
return 0;
}