Linux(Ubuntu)/Windows-C++云备份实现

目录

  • 项目介绍:
  • 概要设计:
    • 技术调研
  • 详细设计:
    • 目录监控模块
      • 样例编写(c++17的filesystem中的文件遍历功能)
    • 数据管理模块
      • 样例编写(unordered_map格式存储)
  • 文件压缩与解压缩模块
    • bundle数据压缩库使用测试
    • 网络通信模块
      • 样例编写(基于httplib库快速搭建)
  • 客户端:
    • 头文件编写(目录遍历,文件读写,数据管理)
    • 主函数
  • 服务端
    • 头文件编写(目录遍历,文件读写,数据管理,文件上传与下载,断点续传,热度文件判断,压缩与解压缩)
    • 主函数

项目介绍:

项目简介:
实现云备份服务器和客户端程序组成一套系统
客户端程序运行在客户端主机,对指定目录下需要备份的文件进行备份上传;
服务器对上传的文件进行备份存储,并对非热点文件进行压缩存储节省磁盘空间,并且支持浏览器访问以及断点续传下载;
项目特点:
http服务器,线程池,读写锁,lz压缩
项目实现:

  • 客户端:
    文件监控模块:采用c++14中的文件系统接口对目录进行遍历获取文件信息。
    数据管理模块:记录备份的文件信息,并且通过信息判断文件是否需要备份。
    文件上传模块:搭建http客户端,使用post请求方式实现文件上传备份。
  • 服务端:
    文件管理模块:对指定目录下的文件遍历判断是否为非热点文件,并进行非热点文件压缩存储。
    数据管理模块:记录管理备份文件信息,实现判断文件是否已经压缩的功能。
    HTTP服务器模块:与客户端进行交互,提供文件上传,查看,下载(断点续传)功能的处理。

在这里插入图片描述

概要设计:

一、客户端

  • 编程环境:Windows 下的客户端程序:Win10-Visual Studio2017;
  • 功能需求:监控目录,自动对指定目录下需要备份的文件进行上传备份;

1.目录监控: 用 C++17filesystem 遍历目录,判断文件是否需要备份,判断方式、条件如下;

  • 获取历史备份信息,获取当前监控目录下文件信息(名称、时间、大小);
  • 判断是否需要备份:当前文件信息与同名历史文件信息不符则需要备份,文件信息未在历史信息中出现需要备份;

2.网络通信: 遍历需要上传备份的文件,获取其文件名称,使用 httplib 库中提供的接口进行文件数据上传;

3.**数据管理:**从第一步中获取到需要备份的文件,将其信息保存到下来(代码内:map,代码外:文件),保存格式为【文件名称 : 创建时间 + 文件大小】的键值对,被保存的文件也相当于历史备份信息,可作为用来判断文件是否需要备份的比对;

二、服务端

  • 编程环境:Linux服务器:Ubuntu18.04.1-vim/g++/makefile ;
  • 功能需求:对客户端上传的文件进行保存,并对文件进行检查,如果是非热点文件,那么就进行压缩存储,删除源文件,支持用户浏览器对备份文件的访问与下载;

1.网络通信: 使用 httplib 库中提供的接口进行接收客户端上传的文件数据,并以文件形式保存;

2.浏览下载: 用户通过浏览器可以查看当前已备份文件,可以对备份文件进行下载,并且下载还支持断点续传;

3.文件压缩: 备份文件在一定时间内没有被访问过,那么该文件就是非热点文件,对其进行压缩存储,判断方法就是用当前时间减去文件最后一次访问时间;

3.数据管理: 压缩存储的文件,会将其对应的原文件删除,所以我们需要保留原文件与压缩文件之间的对应关系(代码内:map,代码外:文件),保存格式为【原文件 : 压缩文件】的键值对;

技术调研

目录监控

  • 1.需要获取目录下所有的文件信息,使用c++17filesystem中的文件遍历功能
  • 2.根据文件信息判断是否需要备份 ①.是否新增;②.是否修改
    程序第一次启动,所有文件都需要进行备份,并且记录信息(文件名称–文件唯一标识(md5–大小+时间))
    之后对获取到的文件信息进行判断:是否在已备份文件信息中 (1).不存在则为新增 (2).存在则通过唯一标识符进行判断是否已修改
  • 3.文件备份
    遍历需要备份的文件信息(获取文件路径名)
    搭建http客户端,上传文件数据,备份到云端

数据管理

  • 代码操作时使用unordered_map进行数据管理;
  • 持久化存储使用文件来进行数据管理。

压缩与解压缩

  • 使用 bundle 实现文件压缩与解压缩;

网络通信

  1. 客户端
  • 创建客户端对象:httplib::Client cli("服务端ip", 服务端port);
  • 实现文件上传与接收:组织httplib::MultipartFormData结构的信息,需要填充的信息有以下四种:

httplib::MultipartFormData data;
data.name:位域信息,根据不同类型的内容设置不同的名字(双方协商),服务端通过这个字段可以判断是否有我需要的信息上传了,而且可以根据这个字段的信息来确定该文件的处理方式;
data.filename:上传文件的真实名称;
data.content:文件内容;
data.content_type:文件格式;

  • 创建httplib::MultipartFormDataItems数组,该数组存放的元素类型为httplib::MultipartFormData,当我们组织好文件信息后,将其添加入该数组中;
  • 使用客户端对象进行上传,上传格式为cli.Post("请求信息", 数组);
  • 客户端通过浏览器进行访问、下载,浏览器请求格式为:ip:port/请求资源
  1. 服务端
  • 创建服务端对象:httplib::Server ser;
  • 为客户端不同的请求信息,创建不同的void(*fun)(const httplib::Request&, httplib::Response&);回调函数;
  • 对于文件上传请求,回调函数如下:
static void Upload(const httplib::Request& req, httplib::Response& res){    //判断上传文件是否存在    if(!req.has_file("上传文件的位域信息,也就是name字段")){//...  }    //存在则获取文件    const httplib::MultipartFormData& file = req.get_file_value("上传文件的位域信息,也就是name字段");    //然后获取文件内容,写入文件,记录备份信息...   //...return;    
} 
  • 对于访问、下载等请求,则是组织相应格式的响应信息即可,需要注意的是,用户访问时展示的是所有备份文件的名字,但是可能有一部分文件已经被压缩存储了,所以下载时需要进行判断,对压缩文件进行解压缩才能下载;
  • 开始监听,格式为:srv.listen("0.0.0.0", 监听port),将监听 IP 设置为 “0.0.0.0” 的目的是,可以监听该主机上任意一个网卡设备的请求,这样做更加便捷可靠;

断点续传

  1. 假设现在用户通过浏览器要下载 xuexi.mp4 文件;
  2. 服务端收到请求后,检查是否为从头开始下载该文件,如果是从头开始下载,则响应信息设置为以下内容:
//回复响应信息的对象为:res
res.set_header("Accept-Ranges", "bytes");//告诉客户端,本服务器支持断点续传
res.set_header("ETag", newflag);//将备份文件的唯一标识返回给客户端,用于后续比较
res.set_header("Content-Type", "application/octet-stream");//下载则设置为二进制传输
res.body = str;//设置响应正文,也就是文件内容
res.status = 200;//成功响应返回200
  1. 客户端在下载的过程中出现了一些意外情况,导致下载中断,此时选择继续下载,因为服务端支持断点续传功能,所以进行断点续传;
  2. 客户端会通过会通过ranges字段向服务端请求需要断点续传的区间是哪些,ranges字段的每一个元素都是pair类型,first-代表了起始位置,second代表了结束位置;
  3. 服务端在收到请求后,判断此次下载为断点续传,则会进行以下操作:
//请求对象为req,响应对象为res
1. 判断该文件是否为断点续传,如果是断点续传,则获取之前传给客户端的文件唯一标识
std::string oldflag;    
if(req.has_header("If-Range")){     oldflag = req.get_header_value("If-Range");    
}
2. 然后拿当前文件唯一标识和旧的文件唯一表示进行比较,如果不相等,则说明备份文件被修改过了,那么就不能断点续传了,因为内容会接不上,需要全部下载,如果相等,则断点续传
if(req.has_header("If-Range") && newflag == oldflag){    //获取断点续传区间,因为只有一个区间,所以不需要循环获取begin = req.ranges[0].first;    end = req.ranges[0].second;    //如果end==-1,则说明是从begin位置到文件结束    if(end == -1){    //修改end为文件末尾,文件大小减一    end = filesize - 1;    }
3. 设置头部3.1 ("Content-Range", "bytes 起始-结束/文件大小")std::stringstream ss;    ss << "bytes " << begin << '-' << end << '/' << filesize;    res.set_header("Content-Range", ss.str());    3.2 将备份文件的唯一标识返回给客户端,用于后续比较res.set_header("ETag", newflag);3.3 下载则设置为二进制传输res.set_header("Content-Type", "application/octet-stream");res.body = str;//设置响应正文,也就是文件内容 res.status = 206;//断点续传成功响应码为206   
}  

详细设计:

目录监控模块

样例编写(c++17的filesystem中的文件遍历功能)

目录遍历的技术选型,以及文件信息的获取(大小,时间属性)
编写目录监控函数代码示例:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <cstdlib>
#ifdef _WIN32
#include <windows.h>
#include <filesystem>
#else
#include <experimental/filesystem>
#include <unistd.h>
#endifvoid ScanDir(const std::string& path)
{if (!std::experimental::filesystem::exists(path)){   std::experimental::filesystem::create_directories(path);}   for (auto& file : std::experimental::filesystem::directory_iterator(path)){   auto& f = file.path();if (std::experimental::filesystem::is_directory(f)){   std::cout << f.filename() << "/\n";continue;}   else{   std::cout << f.filename();}   //获取文件大小 uint64_t size = std::experimental::filesystem::file_size(f);std::cout << "\t" << size;//获取时间auto time_type = std::experimental::filesystem::last_write_time(f);std::time_t t = decltype(time_type)::clock::to_time_t(time_type);std::cout << "\t" << std::asctime(std::localtime(&t)) << std::endl;}return;
}
int main()
{//需要监控的目录const char* path = "./testdir/";while (1){ScanDir(path);
#ifdef _WIN32Sleep(1000);
#elsesleep(1);
#endif}return 0;
}

数据管理模块

在服务端需要对文件信息进行管理,这里采用的是unordered_map进行存储,主要函数有序列化和反序列化。
序列化:对以键值对形式的数据文件信息进行存储,进行写入操作。
反序列化:对之前写入的文件内容读取出来,进行读取操作。

样例编写(unordered_map格式存储)

/**将数据序列化、反序列化样例编写*/
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <unordered_map>
#include <sstream>
#include <experimental/filesystem>
using namespace std;//分割字符,str:被分割字符串,sp:分割符,arry:分割后存入数组
int Split(const string &str, const string &sp, vector<string> *arry)
{int count = 0;size_t pos, idx = 0;while(1){   pos = str.find(sp, idx);if(pos == string::npos){   break;}   string tmp = str.substr(idx, pos-idx);arry->push_back(tmp);idx = pos + sp.size();count++;}   if(idx != str.size()){   arry->push_back(str.substr(idx));count++;}return count;
}
void unseri()
{//反序列化,将文件内容读取出来const char *name = "test.dat";ifstream infile;std::string body;infile.open(name, std::ios::binary);if(infile.is_open() == false){std::cout << "open file failed!\n";return ;}uint64_t size = std::experimental::filesystem::file_size(name);body.resize(size);infile.read(&body[0], size);infile.close();//std::cout << body << std::endl;vector<string> arry;Split(body, "\n", &arry);for(auto &s: arry){//二次分割,将=两端进行分割输出vector<string> kv;Split(s, "=", &kv);std::cout << "[" << kv[0] << "]" << "=" << "[" << kv[1] << "]\n";}
}
void seri()
{unordered_map<string, string> _map = {{"main.cpp", "9913687567"},{"child.cpp", "9913676453"},{"client.cpp", "9913345244"},{"server.cpp", "9913687565"}};stringstream ss;ss.clear();for(auto &info: _map){ss << info.first << "=" << info.second << "\n";}ofstream outfile;outfile.open("test.dat", std::ios::binary);outfile << ss.str();outfile.close();
}
int main()
{//seri();unseri();return 0;
}

文件压缩与解压缩模块

bundle数据压缩库使用测试

bundle是一个GitHub开源的插件压缩库(https://github.com/r-lyeh-archived/bundle),提供文件的压缩和解压方法。可以直接嵌入到代码中,直接使用,支持23种压缩算法和2种存档格式,使用时只需加入bundle.h和bundle.cpp两个文件即可。
编译时,指明编译bundle.cpp,同时因为bundle.cpp中使用了多线程,还需要链接pthread库

g++ -o test test.cc bundle.cpp -std=c++11 -lptrhead

以下进行编写示例:

  • 压缩函数编写示例
    通过对bundle.cpp文件进行压缩,将压缩后的文件命名为bundle.cpp.lz
void compress()
{//将bendle.cpp文件内容进行读取,读取到body里面const char *name = "./bundle.cpp";uint64_t size = std::experimental::filesystem::file_size(name);std::string body;body.resize(size);std::ifstream infile;infile.open(name, std::ios::binary);infile.read(&body[0], size);infile.close();//读取结束后,对bundle.cpp的内容进行压缩,返回压缩后的内容std::string str = bundle::pack(bundle::LZIP, body);//压缩后,将压缩的内容写入到bundle.cpp.lz里面std::ofstream outfile;outfile.open("./bundle.cpp.lz", std::ios::binary);outfile.write(&str[0], str.size());outfile.close();return ;
}

将压缩bundle.cpp.lz进行解压,解压后得到的文件为bundle.cc

  • 解压缩函数编写示例
#include <iostream>
#include <fstream>
#include <experimental/filesystem>
#include "bundle.h"void uncompress()
{const char *name = "./bundle.cpp.lz";//定义解压缩后的文件名const char *name1 = "./bundle.cc";//将压缩文件bundle.cpp.lz的内容读取出来,读取到bodyuint64_t size = std::experimental::filesystem::file_size(name);std::string body;body.resize(size);std::ifstream infile;infile.open(name, std::ios::binary);infile.read(&body[0], size);infile.close();//压缩内容读取出来后将其进行解压缩std::string str = bundle::unpack(body);//将解压缩后的内容写入bundle.cc文件内std::ofstream outfile;outfile.open(name1, std::ios::binary);outfile.write(&str[0], str.size());outfile.close();return ;
}
编写完成之后,将两个函数分别在main.cpp中调用,执行以下命令,因为要用到文件系统所以加上-lstdc++fs

g++ -std=c++17 main.cpp bundle.cpp -o main -lpthread -lstdc++fs

在这里插入图片描述
编译完成后,通过md5查看原文件和解压缩后的文件大小是否一致。

网络通信模块

样例编写(基于httplib库快速搭建)

  • 客户端
    对需要上传的文件信息组织成httplib::MultipartFormData格式后,加入到httplib::MultipartFormDataItems数组,再使用客户端对象进行上传。
#include <iostream>
#include <fstream>
#include "httplib.h"using namespace httplib;void ReadFile(const std::string &path, std::string *body)
{std::ifstream infile;infile.open(path);//跳转到文件末尾infile.seekg(0,std::ios::end);//获取偏移量-文件长度long len = infile.tellg();//跳转到文件起始infile.seekg(0, std::ios::beg);//申请空间body->resize(len);//读取文件数据infile.read(&(*body)[0], len);//关闭文件infile.close();
}
int main()
{const char *name = "./test.mp3";//创建客户端对象Client cli("127.0.0.1",9000);//组织结构信息MultipartFormDataItems items;MultipartFormData file1;file1.name = "file1";file1.filename = "out_test.mp3";file1.content_type = "application/octet-stream";ReadFile(name, &file1.content);items.push_back(file1);//使用客户端进行上传cli.Post("/Multipart", items);return 0;
}
  • 服务端
    针对客户端不同的请求,创建不同的回调函数,如针对/Multipart的资源请求,创建对应的回调函数void Upload(const Request &req, Response &rsp)
    该回调函数实现进行判断文件是否存在,存在则将文件写入进行保存。
#include<iostream>
#include<string>
#include<fstream>
#include"httplib.h"using namespace httplib;
void WriteFile(const std::string &path, const std::string &body)
{std::ofstream outfile;outfile.open(path, std::ios::binary);outfile.write(&body[0], body.size());outfile.close();
}
void Upload(const Request &req, Response &rsp)
{auto ret = req.has_file("file1");if(ret == false){   std::cout << "have no file\n";return;}   const auto& file = req.get_file_value("file1");WriteFile(file.filename, file.content);// file.filename;// file.content_type;// file.content;
}
int main()
{Server srv;srv.Post("/multipart", Upload);srv.listen("127.0.0.1", 9000);return 0;
}

客户端:

头文件编写(目录遍历,文件读写,数据管理)

#pragma once
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <unordered_map>
#ifdef _WIN32
#include <filesystem>
#include <windows.h>
#else
#include <experimental/filesystem>
#include <unistd.h>
#endifnamespace cloud_sys
{namespace fs=std::experimental::filesystem;class ScanDir{private:std::string _path;public:ScanDir(const std::string &path): _path(path){//目录不存在则创建if(!fs::exists(_path)){fs::create_directories(_path);}//判断路径名最后是否为/如果不是则加上if(_path.back() != '/'){_path += '/';}}//开始浏览,需要返回目录里面的所有文件名称,在arry数组内bool Scan(std::vector<std::string> *arry){for(auto &file: fs::directory_iterator(_path)){std::string name;name = file.path().filename().string();std::string pathname = _path + name;//如果是目录,则不进行添加if (fs::is_directory(pathname)){continue;}arry->push_back(_path + name);}return true;}};class Util{public://将文件信息读取出来,存到bodystatic bool FileRead(const std::string &file, std::string *body){body->clear();std::ifstream infile;//以二进制方式打开,避免读出和写入的大小不同,因为ifsteam以字符为单位,一个字符可能占多个字节infile.open(file, std::ios::binary);if(infile.is_open() == false){std::cout << "open file failed!\n";return false;}uint64_t size = fs::file_size(file);body->resize(size);infile.read(&(*body)[0], size);if(infile.good() == false){std::cout << "read file failed!\n";return false;}infile.close();return true;}//将body信息写入到文件内static bool FileWrite(const std::string &file, const std::string &body){std::ofstream outfile;outfile.open(file, std::ios::binary);if(outfile.is_open() == false){std::cout << "open file failed!\n";return false;}outfile.write(&body[0], body.size());if(outfile.good() == false){std::cout << "write file failed!\n";return false;}outfile.close();return true;}//将信息进行分割,传入三个参数,1被分割内容.2分割符.3分割后的内容static int Split(const std::string &str,const std::string &sp,std::vector<std::string> *arry){//定义文件个数int count = 0;//定义分隔符位置和偏移量size_t pos, idx = 0;while(1){//从idx起始位置开始找pos = str.find(sp, idx);if(pos == std::string::npos)break;std::string tmp;//字符串截取,从偏移量idx开始到pos的位置tmp = str.substr(idx, pos-idx);arry->push_back(tmp);//更新偏移量idxidx = pos + sp.size();count++;}//如果分隔符找到字符串末尾还没有,那么将偏移量到字符串末尾的数据全部插入arryif(idx < str.size()){arry->push_back(str.substr(idx));count++;}return count;}};class DataManager{private:std::string _path;std::unordered_map<std::string, std::string> _map;public:DataManager(const std::string &path): _path(path){}//把文件数据读取出来存入到_map中bool Read(){std::string body;std::vector<std::string> arry;//调用Util类中的文件读取函数,将文件信息读到bodyif(Util::FileRead(_path, &body) == false){std::cout << "read data set failed!\n";return false;}//将body内的文件信息进行拆分//先按行进行拆分,将每个文件信息保存arry数组Util::Split(body, "\n", &arry);//对arry数据进行循环遍历,针对每一行的文件信息进一步拆分for(auto &line: arry){//定义kv数据存储每行文件信息的k和v值std::vector<std::string> kv;Util::Split(line, "=", &kv);_map[kv[0]] = kv[1];}return true;}//把_map数据写入到文件中bool Write(){std::stringstream ss;for(auto &it: _map){ss << it.first << "=" << it.second << "\n";}if(Util::FileWrite(_path, ss.str()) == false){std::cout << "write data set failed!\n";return false;}return true;}bool Exists(const std::string &key){auto it = _map.find(key);if(it == _map.end()){return false;}return true;}bool AddOrMod(const std::string &key, const std::string &val){_map[key] = val;return true;}bool Del(const std::string &key, const std::string &val){auto it = _map.find(key);if(it == _map.end()){std::cout << key << "not exists!\n";return false;}_map.erase(it);return true;}bool Get(const std::string &key, std::string *val){auto it = _map.find(key);if(it == _map.end()){std::cout << key << "not exitsts!\n";return false;}*val = _map[key];return true;}};#define LISTEN_DIR "./scandir"
#define CONFIG_FILE "./data.conf"class Client{private:ScanDir _scan;	//给与监控的目录路径DataManager _data;	//给与配置文件的路径httplib::Client *_client;public:Client(const std::string &host, int port): _scan(LISTEN_DIR), _data(CONFIG_FILE){_client = new httplib::Client(host, port);}//获取文件唯一标识:大小+时间std::string GetIdentifier(std::string &path){uint64_t mtime, fsize;fsize = fs::file_size(path);auto time_type = fs::last_write_time(path);mtime = decltype(time_type)::clock::to_time_t(time_type);std::stringstream ss;ss << fsize << mtime;return ss.str();}//获取需要备份的文件信息,参数值返回:不仅需要备份文件名,还要备份文件的唯一标识(pair实现)bool Scan(std::vector<std::pair<std::string, std::string>> *arry){//获取所有的文件名std::vector<std::string> files;_scan.Scan(&files);//对所有文件名进行遍历,对每个文件进行判断是否存在、是否进行修改:不存在或不一致则进行备份for (auto &file : files){//针对每个文件进行获取对应的标识std::string identifier = GetIdentifier(file);//判断是否存在if (_data.Exists(file) == false){//不存在进行备份,去判断下一个arry->push_back(std::make_pair(file, identifier));continue;}//存在进行判断是否一致std::string old;_data.Get(file, &old);//如果一致,跳过判断下一个文件if (old == identifier){continue;}//不一致进行备份arry->push_back(std::make_pair(file, identifier));}return true;}//上传指定的文件bool Upload(const std::string &path){//组织上传数据格式httplib::MultipartFormData file;//name字段的作用是位域的区分,用来区分是什么文件类型file.name = "file";file.content_type = "application/octet-stream";fs::path p(path);file.filename = p.filename().string();Util::FileRead(path, &file.content);httplib::MultipartFormDataItems items;items.push_back(file);auto rsp = _client->Post("/multipart", items);if (rsp && rsp->status == 200){return true;}return false;}bool Start(){//1.读取历史备份信息(如果有)_data.Read();while (1){//2.通过Scan接口获取到需要备份的文件信息(文件路径名,唯一标识)--数组std::vector<std::pair<std::string, std::string>> arry;Scan(&arry);//遍历需要备份的文件信息,然后逐个进行备份for (auto &file : arry){std::cout << file.first << "需要进行文件备份\n";//通过Upload接口对需要备份的文件进行上传备份if (Upload(file.first) == false){//上传失败则不能修改备份信息,等下次遍历这个文件就会检测不一致重新进行上传continue;}//记录备份成功的文件的备份信息_data.AddOrMod(file.first, file.second);//修改持久化存储信息_data.Write();}Sleep(1000);}return true;}};
}

主函数

#include "cloud.hpp"int main()
{cloud_sys::Client client("192.168.159.129", 9000);client.Start();return 0;
}

Windows下查看md5值:certutil -hashfile cloud.cpp md5

服务端

头文件编写(目录遍历,文件读写,数据管理,文件上传与下载,断点续传,热度文件判断,压缩与解压缩)

#pragma once
#include <iostream>
#include <fstream>
#include <sstream>
#include <string>
#include <vector>
#include <unordered_map>
#ifdef _WIN32
#include <filesystem>
#include <windows.h>
#else
#include <experimental/filesystem>
#include <unistd.h>
#include <sys/stat.h>
#include <pthread.h>
#include "httplib.h"
#include "bundle.h"
#endifnamespace cloud_sys
{namespace fs=std::experimental::filesystem;class ScanDir{   private:std::string _path;public:ScanDir(const std::string &path): _path(path){   //目录不存在则创建if(!fs::exists(_path)){   fs::create_directories(_path);}   //判断路径名最后是否为/如果不是则加上if(_path.back() != '/'){   _path += '/';}   }   //开始浏览,需要返回目录里面的所有文件名称,在arry数组内bool Scan(std::vector<std::string> *arry){   for(auto &file: fs::directory_iterator(_path)){   std::string name;name = file.path().filename().string();arry->push_back(_path + name);}   return true;}   };class Util{public:static bool RangeRead(const std::string &file, std::string *body, int *start, int *end){body->clear();std::ifstream infile;infile.open(file, std::ios::binary);if(infile.is_open() == false){std::cout << "open file failed!\n";return false;}uint64_t fsize = fs::file_size(file);uint64_t readlen;if(*end == -1){*end = fsize - 1;}readlen = *end - *start + 1;//从起始位置偏移到start位置infile.seekg(*start, std::ios::beg);body->resize(readlen);infile.read(&(*body)[0], readlen);if(infile.good() == false){std::cout << "read file failed!\n";return false;}infile.close();return true;}//将文件信息读取出来,存到bodystatic bool FileRead(const std::string &file, std::string *body){body->clear();std::ifstream infile;//以二进制方式打开,避免读出和写入的大小不同,因为ifsteam以字符为单位,一个字符可能占多个字节infile.open(file, std::ios::binary);if(infile.is_open() == false){std::cout << "open file failed!\n";return false;}uint64_t size = fs::file_size(file);body->resize(size);infile.read(&(*body)[0], size);if(infile.good() == false){std::cout << "read file failed!\n";return false;}infile.close();return true;}//将body信息写入到文件内static bool FileWrite(const std::string &file, const std::string &body){std::ofstream outfile;outfile.open(file, std::ios::binary);if(outfile.is_open() == false){std::cout << "open file failed!\n";return false;}outfile.write(&body[0], body.size());if(outfile.good() == false){std::cout << "write file failed!\n";return false;}outfile.close();return true;}//将信息进行分割,传入三个参数,1被分割内容.2分割符.3分割后的内容static int Split(const std::string &str,const std::string &sp,std::vector<std::string> *arry){//定义文件个数int count = 0;//定义分隔符位置和偏移量size_t pos, idx = 0;while(1){//从idx起始位置开始找pos = str.find(sp, idx);if(pos == std::string::npos)break;std::string tmp;//字符串截取,从偏移量idx开始到pos的位置tmp = str.substr(idx, pos-idx);arry->push_back(tmp);//更新偏移量idxidx = pos + sp.size();count++;}//如果分隔符找到字符串末尾还没有,那么将偏移量到字符串末尾的数据全部插入arryif(idx < str.size()){arry->push_back(str.substr(idx));count++;}return count;}};class DataManager{private:std::string _path;//map设计在多线程中访问,为了解决安全问题需要加锁//互斥锁只能互斥访问,效率较低//因此采用读写锁---读共享,写互斥std::unordered_map<std::string, std::string> _map;pthread_rwlock_t _rwlock;public:DataManager(const std::string &path): _path(path){pthread_rwlock_init(&_rwlock, NULL);}~DataManager(){pthread_rwlock_destroy(&_rwlock);}//把文件数据读取出来存入到_map中bool Read(){std::string body;std::vector<std::string> arry;//调用Util类中的文件读取函数,将文件信息读到bodyif(Util::FileRead(_path, &body) == false){std::cout << "read data set failed!\n";return false;}//将body内的文件信息进行拆分//先按行进行拆分,将每个文件信息保存arry数组Util::Split(body, "\n", &arry);//对arry数据进行循环遍历,针对每一行的文件信息进一步拆分for(auto &line: arry){//定义kv数据存储每行文件信息的k和v值std::vector<std::string> kv;Util::Split(line, "=", &kv);//加锁pthread_rwlock_wrlock(&_rwlock);_map[kv[0]] = kv[1];pthread_rwlock_unlock(&_rwlock);}return true;}//把_map数据写入到文件中bool Write(){std::stringstream ss;pthread_rwlock_rdlock(&_rwlock);for(auto &it: _map){ss << it.first << "=" << it.second << "\n";}pthread_rwlock_unlock(&_rwlock);if(Util::FileWrite(_path, ss.str()) == false){std::cout << "write data set failed!\n";return false;}return true;}bool Exists(const std::string &key){pthread_rwlock_rdlock(&_rwlock);auto it = _map.find(key);if(it == _map.end()){pthread_rwlock_unlock(&_rwlock);return false;}pthread_rwlock_unlock(&_rwlock);return true;}//添加数据到_map中bool AddOrMod(const std::string &key, const std::string &val){pthread_rwlock_wrlock(&_rwlock);_map[key] = val;pthread_rwlock_unlock(&_rwlock);return true;}//删除数据bool Del(const std::string &key, const std::string &val){pthread_rwlock_wrlock(&_rwlock);auto it = _map.find(key);if(it == _map.end()){pthread_rwlock_unlock(&_rwlock);std::cout << key << "not exists!\n";return false;}_map.erase(it);pthread_rwlock_unlock(&_rwlock);return true;}bool Get(const std::string &key, std::string *val){pthread_rwlock_rdlock(&_rwlock);auto it = _map.find(key);if(it == _map.end()){pthread_rwlock_unlock(&_rwlock);std::cout << key << "not exitsts!\n";return false;}*val = _map[key];pthread_rwlock_unlock(&_rwlock);return true;}bool GetAll(std::vector<std::string> *arry){arry->clear();pthread_rwlock_rdlock(&_rwlock);for(auto &file: _map){arry->push_back(file.first);}pthread_rwlock_unlock(&_rwlock);return true;}};class Compress{public:static bool Pack(const std::string &filename, const std::string &packname){std::string body;Util::FileRead(filename, &body);Util::FileWrite(packname, bundle::pack(bundle::LZIP, body));return true;}static bool UnPack(const std::string &packname, const std::string &filename){std::string body;Util::FileRead(packname, &body);Util::FileWrite(filename, bundle::unpack(body));return true;}};
#define BACKUP_PATH "./backup/"
#define CONFIG_PATH "./backup.conf"DataManager g_data(CONFIG_PATH);class Server{private:httplib::Server _srv;private:static void Upload(const httplib::Request &req, httplib::Response &rsp){//判断有无对应name的文件上传信息auto ret = req.has_file("file");if(ret == false){std::cout << "have no file\n";rsp.status == 400;return ;}if(fs::exists(BACKUP_PATH) == false){fs::create_directories(BACKUP_PATH);}const auto& file = req.get_file_value("file");//设置文件的存储路径./backup/test.txtstd::string filename = BACKUP_PATH + file.filename;if(Util::FileWrite(filename, file.content) == false){std::cout << "write file data failed!\n";rsp.status = 500;return ;}//上传文件名,实际存储文件名--在被压缩后就会改为压缩包名称g_data.AddOrMod(file.filename, file.filename);  //添加备份信息g_data.Write();return ;}static void List(const httplib::Request &req, httplib::Response &rsp){std::stringstream ss;ss << "<html><head><meta http-equiv='content-type' content='text/html;charset=utf-8'>";ss << "</head><body>";std::vector<std::string> arry;g_data.GetAll(&arry);for(auto &filename: arry){//<a href='/download/main.txt'><strong>main.txt</strong></a>ss << "<hr />";ss << "<a href='/backup/" << filename << "'><strong>" << filename << "</strong></a>";}ss << "<hr /></body></html>";rsp.body = ss.str();rsp.set_header("Content-Type", "text/html");return ;}static std::string GetIdentifier(std::string &path){uint64_t mtime, fsize;fsize = fs::file_size(path);auto time_type = fs::last_write_time(path);mtime = decltype(time_type)::clock::to_time_t(time_type);std::stringstream ss;ss << fsize << mtime;return ss.str();}static void Download(const httplib::Request &req, httplib::Response &rsp){//std::cout << "into Download" << req.matches[1] << std::endl;std::string name = req.matches[1];std::string pathname = BACKUP_PATH + name;std::string newetag = GetIdentifier(pathname);uint64_t fsize = fs::file_size(pathname);//判断是否压缩文件,如果是则进行解压缩                if(g_data.Exists(name)){std::string realname;g_data.Get(name, &realname);if(name != realname){Compress::UnPack(realname, pathname);unlink(realname.c_str());g_data.AddOrMod(name, name);g_data.Write();}}if(req.has_header("If_Range")){std::string oldetag = req.get_header_value("If-Range");if(oldetag == newetag){//断点续传--获取区间范围std::cout << req.ranges[0].first << "-" << req.ranges[0].second << "\n";int start = req.ranges[0].first;int end = req.ranges[0].second;Util::RangeRead(pathname, &rsp.body, &start, &end);rsp.set_header("Content-Type", "application/octet-stream");rsp.set_header("ETag", newetag);std::stringstream ss;ss << "bytes" << start << "-" << end << "/" << fsize;rsp.set_header("Content-Range", ss.str());rsp.status = 206;return ;}}if(Util::FileRead(pathname, &rsp.body) == false){std::cout << "read file" << pathname << "failed\n";rsp.status = 500;return ;}rsp.set_header("Content-Type", "application/octet-stream");rsp.set_header("Accept-Ranges", "bytes");rsp.set_header("ETag", newetag);rsp.status = 200;return ;}public:bool Start(int port = 9000){g_data.Read();_srv.Post("/multipart", Upload);_srv.Get("/list", List);_srv.Get("/backup/(.*)", Download);_srv.listen("0.0.0.0", port);return true;}};
#define PACK_PATH "./packdir/"class FileManager{private:ScanDir _scan;time_t _hot_time = 60;private:time_t LastAccessTime(const std::string &filename){struct stat st;stat(filename.c_str(), &st);return st.st_atime;}public:FileManager(): _scan(BACKUP_PATH){}bool Start(){while(1){std::vector<std::string> arry;_scan.Scan(&arry);for(auto &file : arry){//文件最后一次访问时间time_t atime = LastAccessTime(file);//当前系统时间time_t ctime = time(NULL);if(ctime - atime > _hot_time){fs::path fpath(file);std::string pack = PACK_PATH + fpath.filename().string() + ".pack";std::cout << file << " <-压缩存储-> " << pack << std::endl;Compress::Pack(file, pack); //压缩存储文件unlink(file.c_str());   //删除源文件g_data.AddOrMod(fpath.filename().string(), pack);    //修改备份信息g_data.Write();}}usleep(1000);}return true;}};
}

主函数

由于http服务器是个死循环,文件管理中的start也是一个死循环(用来不断判断热度文件),因此使用多线程。

#include "cloud.hpp"
#include <thread>void thread_start()
{cloud_sys::FileManager fm; fm.Start();
}
int main()
{std::thread scan_thr(thread_start);scan_thr.detach();cloud_sys::Server srv;srv.Start();return 0;
}

执行命令:

g++ -std=c++14 cloud.cpp bundle.cpp -o cloud -lpthread -lstdc++fs

最终项目目录下的文件如下
在这里插入图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/375651.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【C++】开源:paho-mqtt-cpp库配置与使用

&#x1f60f;★,:.☆(&#xffe3;▽&#xffe3;)/$:.★ &#x1f60f; 这篇文章主要介绍paho-mqtt-cpp库配置与使用。 无专精则不能成&#xff0c;无涉猎则不能通。——梁启超 欢迎来到我的博客&#xff0c;一起学习&#xff0c;共同进步。 喜欢的朋友可以关注一下&#xff…

【qt】客户端连接到服务器

获取到IP地址和端口号. 通过connectToHost() 来进行连接. 对于客户端来讲,只需要socket即可. 客户端连接服务端只需要使用套接字(Socket)来进行通信。客户端通过创建一个套接字来连接服务端&#xff0c;然后可以通过套接字发送和接收数据。套接字提供了一种简单而灵活的方式来…

pycharm 占满磁盘

磁盘里没装什么大文件&#xff0c;发现磁盘被占的越来越满&#xff0c;使用工具查看到底是哪个文件如此之大。 发现罪魁祸首是pycharm&#xff01;&#xff01;&#xff01; 根据工具的提示找到对应的路径文件&#xff1a;E:\pycharm\PyCharmCE2022.3\python_packages 发现pa…

海外短剧开源系统UNIAPP源码(支持多语言/海外支付/快捷登录)

给大家推荐一款非常好的海外短剧系统源码&#xff0c;这款源码案例应该最多的。 他们的开源地址&#xff1a;https://gitee.com/qiao-yonggang/haiwaiduanju 这套系统的优势&#xff1a; 支持世界的所有语言&#xff0c;可后台自定义设置支持paypal stripe pix等多种海外支付…

Redis的配置优化、数据类型、消息队列

文章目录 一、Redis的配置优化redis主要配置项CONFIG 动态修改配置慢查询持久化RDB模式AOF模式 Redis多实例Redis命令相关 二、Redis数据类型字符串string列表list集合 set有序集合sorted set哈希hash 三、消息队列生产者消费者模式发布者订阅者模式 一、Redis的配置优化 redi…

easyExcel 不规则模板导入数据

文章目录 前言一、需求和效果二、难点和思路三、全部代码踩坑 前言 之前分享的 EasyExcel 批量导入并校验数据&#xff0c;仅支持规则excel&#xff0c;即首行表头&#xff0c;下面对应数据&#xff0c;无合并单元格情况。 本篇主要解决问题&#xff1a; 模板excel 表头不在首…

基于PCIe总线架构的2路1GSPS AD、4路1GSPS DA信号处理平台(100%国产化)

板卡概述 PCIE723-165是基于PCIE总线架构的2通道1GSPS采样率14位分辨率、4通道1GSPS采样率16位分辨率信号处理平台&#xff0c;该板卡采用国产16nm FPGA作为实时处理器&#xff0c;支持2路高速采集以及4路高速数据回放&#xff0c;板载2组DDR4 SDRAM大容量数据缓存&#xff0c;…

ElasticSearch 深度分页详解

原文链接&#xff1a;https://zhuanlan.zhihu.com/p/667036768 1 前言 ElasticSearch 是一个实时的分布式搜索与分析引擎&#xff0c;常用于大量非结构化数据的存储和快速检索场景&#xff0c;具有很强的扩展性。纵使其有诸多优点&#xff0c;在搜索领域远超关系型数据库&…

IDEA 中的调试方式(以 java 为例)

文章目录 IDEA 中的调试方式(以 java 为例)1. 基本介绍2. 断点调试的快捷键2.1 设置断点并启动调试2.3 快捷键 IDEA 中的调试方式(以 java 为例) 在开发中查找错误的时候&#xff0c;我们可以用断点调试&#xff0c;一步一步的看源码执行的过程&#xff0c;从而发现错误所在。 …

Linux --- 高级IO

一、基本概念 五种IO模型 阻塞IO&#xff1a;在内核将数据准备好之前&#xff0c;系统调用会一直等待&#xff0c;所有的套接字&#xff0c;默认都是阻塞方式 非阻塞IO&#xff1a;如果内核还未将数据准备好&#xff0c;系统调用仍然会直接返回&#xff0c;并且返回EWOULDBLOCK…

java—Spring框架

Spring 简介 Spring框架由Rod Johnson开发&#xff0c;2004年发布了Spring框架的第一版。Spring是一个从实际开发中抽取出来的框架&#xff0c;因此它完成了大量开发中的通用步骤&#xff0c;留给开发者的仅仅是与特定应用相关的部分&#xff0c;从而大大提高了企业应用的开发…

MacOS 开发 — Packages 程序 macOS新版本 演示选项卡无法显示

MacOS 开发 — Packages 程序 macOS新版本 演示选项卡无法显示 问题描述 &#xff1a; 之前写过 Packages 的使用以及如何打包macOS程序。最近更新了新的macOS系统&#xff0c;发现Packages的演示选项卡无法显示&#xff0c;我尝试从新安转了Packages 也是没作用&#xff0c;…

2024年新一代WebOffice内嵌网页组件——猿大师办公助手

背景 WebOffice控件这个中间件软件产品已存在二十余年&#xff0c;在国内众多大中小型企业、各级政府机关、科研机构和学校等事业单位的OA、ERP、文档系统、云盘等信息化B/S系统中得到了大量使用&#xff0c;为我国的信息化事业也做出了不小贡献。随着操作系统、浏览器及Offic…

亚信安全新一代终端安全TrustOne2024年重磅升级

以极简新主义为核心&#xff0c;亚信安全新一代终端安全TrustOne自2023年发布以来&#xff0c;带动了数字化终端安全的革新。60%&#xff0c;安装部署及管理效率的提升&#xff1b;50%&#xff0c;安全管理资源的节省&#xff1b;100%&#xff0c;信创非信创场景的全覆盖。Trus…

sqlmap使用之-post注入、head注入(ua、cookie、referer)

1、post注入 1.1、方法一&#xff0c;通过保存数据包文件进行注入 bp抓包获取post数据 将数据保存到post.txt文件 加上-r指定数据文件 1.2、方法二、通过URL注入 D:\Python3.8.6\SQLmap>python sqlmap.py -u "http://localhost/login.php" --data "userna…

百度文心4.0 Turbo开放,领跑国内AI大模型赛道!

百度文心4.0 Turbo开放&#xff0c;领跑国内AI大模型赛道&#xff01; 前言 文心一言大模型 就在7月5日&#xff0c;在2024世界人工智能大会 (WAIC) 上&#xff0c;百度副总裁谢广军宣布文心大模型4.0 Turbo正式向企业客户全面开放&#xff01;这一举动直接引发了业界的关注。那…

SpringCloud教程 | 第八篇: 使用seata处理分布式事务

1、参考程序员江小北 2、打算降低nacos版本&#xff0c;先学通再看看升级到高版本nacos能不能正常使用。 3、nacos用1.4.1&#xff0c;正常启动单机版的了 4、seata用2.0.0 我看江小北说用的1.4.0的seata&#xff0c;但是图片中的目录文件都找不到&#xff0c;倒是在2.0.0的…

【git】:初识Git 和 Git 的安装

目录 学习 Git 的目标 Git 安装 Linux-centos Linux-ubuntu Windows 学习 Git 的目标 技术目标 掌握 Git 企业级应用&#xff0c;深刻理解Git操作过程与操作原理&#xff0c;理解工作区&#xff0c;暂存区&#xff0c;版本库的含义 掌握 Git 版本管理&#xff0c;自由进⾏…

DWG文件发布至IIS后无法下载和预览解决办法

问题描述 DWG文件发布至IIS后无法下载和预览 原因分析&#xff1a; iis里面需要添加扩展 解决方案&#xff1a; 在服务器端IS属性的HTTP头下的MIME内容中添加扩展名“.dwg” MIME类型填入application/acad

利用Python进行数据分析PDF下载经典数据分享推荐

本书由Python pandas项目创始人Wes McKinney亲笔撰写&#xff0c;详细介绍利用Python进行操作、处理、清洗和规整数据等方面的具体细节和基本要点。第2版针对Python 3.6进行全面修订和更新&#xff0c;涵盖新版的pandas、NumPy、IPython和Jupyter&#xff0c;并增加大量实际案例…