【云备份项目】

文章目录

    • @[TOC](文章目录)
  • 一、项目框架定义
    • 1.项目需求
    • 2. 服务端框架搭建思想
    • 3.客户端框架搭建思想
  • 二、环境搭建
    • 1.gcc编译器升级
    • 2.安装第三方库
  • 三、认识第三方库
    • 1.json库使用
    • 2.bundle库使用
    • 3.httplib库使用
    • 4.简单服务器搭建
    • 5.简单客户端搭建
  • 四、文件使用工具类设计
    • 1.类的功能结构
    • 2.功能实现
    • 3.常见压缩算法
  • 五、json工具类实现
  • 六、服务端配置信息模块
    • 1.设计思想
    • 2.实现
  • 七、服务端数据管理模块
    • 1.设计思想
    • 2.实现
  • 八、服务端热点管理模块
    • 1.设计思想
    • 2.代码
  • 九、服务端业务处理模块
    • 1.实现思想
    • 2.代码
  • 十、客户端数据模块
    • 1.实现思想
    • 2.代码
  • 十一、客户端业务处理模块
  • 十二、项目类关系图
    • 在这里插入图片描述

一、项目框架定义

1.项目需求

实现本地文件通过客户端上传到网络服务器主机中,客户端也可以实现通过请求服务器查看上传的文件,可以下载云备份的文件,如果下载过程中客户端与服务器终端,还有断点续传功能,就是可以接着中断前的进度下载.

2. 服务端框架搭建思想

按照功能划分:服务端要支持客户端文件上传,备份文件列表查看,文件下载,断点续传,热点文件识别和管理功能,当文件长期不被访问时,对备份文件压缩存储.
按照模块划分:有数据管理模块,负责备份数据的信息管理,网络通信模块,负责与客户端建立通信,业务处理模块,负责文件长传,列表查看,文件下载功能,热点管理模块,负责对

3.客户端框架搭建思想

按照功能划分:客户端要支持指定目录/文件夹的文件检测,如果目录中有文件就将文件获取上传,判断文件是否需要备份,最新一次的上传而且是修改过的文件,并且经过一段时间间隔仍然没有修改的文件就备份.将需要备份的文件上传到服务器.
按照模块儿划分,有数据管理模块,管理备份过的文件信息,文件检测模块,监控指定目录是否有文件,判断文件是否需要备份.文件上传模块.

二、环境搭建

1.gcc编译器升级

sudo yum install -y centos-release-scl-rh centos-release-scl命令获取源信息
sudo yum install -y devtool-7-gcc devtool-7-gcc-c++命令安装7.3版本的gcc和g++
source /opt/rh/devtoolset-7/enable 临时环境配置
echo “source /opt/rh/devtoolset-7/enable” >> ~/.bashrc 将临时环境配置信息追加重定向到.bashrc文件完成永久配置.
使用g++ -v gcc -v验证安装成功

2.安装第三方库

sudo yum install epel-release
sudo yum install jsoncpp-devel安装jsonc库
ls /usr/include/jsoncpp/json/json.h验证是否安装成功
sudo yum install -y git 安装git工具
git clone https://github.com/r-lyeh-archived/bundle.git 从github获取bundle压缩包
git clone https://github.com/yhirose/cpp-httplib.git 从github获取httplib压缩包
bundle库是嵌入式库,就是不用包含头文件,直接将源码拿过来使用的

三、认识第三方库

1.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;//转floatbool 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;
}

Json库的序列化和反序列化类有新老两个版本,推荐是用最新版charReader 反序列化类,和StreamWriter序列化类.,这两个类的实例化对象需要用CharReaderBuilder类和StreamWriterBuilder类的成员函数创建.,其中writer函数需要用到ostringstream类进行序列化操作.

序列化和反序列化都要用到Json的Value类,被序列化和反序列化的数据首先要存储在Value对象中格式化保存,Value类中重载了[]参数const char* 返回值Value类型,还重载了[]参数int index,返回值Value.以及类型转换函数,asString(),asInt (),asFloat()等分别会将Value内的成员转换为字符串型数据,整型数据,浮点型数据.

序列化

#include<iostream>
#include<sstream>
#include<string>
#include<memory>
#include<jsoncpp/json/json.h>int main()
{const char*name="张三";int age = 15;float score[]={89,99,55.7};Json::Value root;root["姓名"]=name;root["年龄"]= age;root["成绩"].append(score[0]);root["成绩"].append(score[1]);root["成绩"].append(score[2]);Json::StreamWriterBuilder swb;std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());std::ostringstream os;sw->write(root,&os);std::string str=os.str();std::cout<<str<<std::endl;return 0;
}

反序列化

#include<iostream>
#include<sstream>
#include<memory>
#include<jsoncpp/json/json.h>
#include<string>int main()
{std::string str=R"({"姓名":"张三","年龄":15,"成绩":[89.1,88,133]})";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 size=root["成绩"].size();for(int i=0;i<size;i++){std::cout<<root["成绩"][i].asFloat()<<std::endl;}
}

2.bundle库使用

bundle库是嵌入式库,可以直接将源码拿过来使用,使用时可以将源文件拷贝过来复用.
压缩
在读取文件的时候,因为要一次读取整个文件,所以要通过获取文件指针偏移量的方式来获取文件大小,以便设置接收文件内容的缓冲区的大小.
文件的binary打开模式,会自动创建不存在的文件.
fstream的读写功能参数只涉及到缓冲区地址和大小.
pack参数一个事压缩文件的类型,一个是要压缩的内容.

#include<iostream>
#include<fstream>
#include"bundle.h"int main(int argc,char* argv[])
{//接收压缩文件std::string compressfile=argv[1];std::string newfile=argv[2];//打开压缩文件std::ifstream ifs(compressfile,std::ios::binary);//将文件指针偏移到文件末尾ifs.seekg(0,std::ios::end);//获取文件偏移量(文件字节大小)int filesize=ifs.tellg();//将文件指针偏移到文件起始ifs.seekg(0,std::ios::beg);//接收文件内容std::string content;content.resize(filesize);ifs.read(&content[0],filesize);ifs.close();//获取文件大小std::string packa=bundle::pack(bundle::LZIP,content);//打开存储文件std::ofstream ofs(newfile,std::ios::binary);//将pack文件写入文件ofs.write(&packa[0],packa.size());//关闭文件ifs.close();return 0;
}

解压缩

#include<string>
#include<fstream>
#include"bundle.h"int main(int argc,char* argv[])
{//接收文件名std::string compressfile=argv[1];std::string newfile=argv[2];//打开压缩文件std::ifstream ifs(compressfile,std::ios::binary);//将文件指针移动至末尾ifs.seekg(0,std::ios::end);//获取文件大小size_t filesize=ifs.tellg();//将文件指针移动至起始ifs.seekg(0,std::ios::beg);//设置接收字符串大小std::string body;body.resize(filesize);//读取压缩文件内容ifs.read(&body[0],filesize);//解压内容std::string packa=bundle::unpack(body);//打开解压文件std::ofstream ofs(newfile,std::ios::binary);//将解压内容写入文件ofs.write(&packa[0],packa.size());//关闭两个文件ifs.close();ofs.close();return 0;
}

最后可以用md5sum 文件名 命令来验证压缩后再解压缩是不是与源文件相同,md5sum是将文件内容和进行哈希映射值.

3.httplib库使用

Request类认识
作用:客户端将用户请求格式化为http请求相关信息,最终组织http请求发送
服务端收到http之后,解析请求,将解析的数据保存在Resquest类中等待处理.

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 serverstd::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;//获取文件的值};

Response类认识
格式化响应,组织响应

    struct Response {std::string version;//服务器版本号int status = -1; //状态码std::string reason;//原因Headers headers;//头部信息std::string body;//主体信息std::string location; // 重定向的地址void set_header(const char *key, const char *val);//设置头部信息void set_content(const std::string &s, const char *content_type);//设置内容};

server类认识
用于搭建服务器

 class Server {//请求处理回调函数类型using Handler = std::function<void(const Request &, Response &)>;//请求路由表类型,regex:正则表达式,用于匹配http资源路径,handler回调函数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);};

client类认识
搭建客户端

    class Client {//接收IP和端口号Client(const std::string &host, int port);//向服务器发送GET请求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);}
}

http::Server原理:当启动Server对象时,会创建并绑定套接字,设置监听,循环接收客户端链接请求,接收到客户端请求时,会创建新的线程,新线程从找到用户传递的回调处理函数,解析客户端请求字段,包括请求方法,URL,版本号,请求包头,主体.设置响应字段.

4.简单服务器搭建

#include<iostream>
#include"httplib.h"
#include<string>int main()
{httplib::Server sr;//请求方法为GET,请求内容为纯文本sr.Get("/hi",[&](const httplib::Request& req,httplib::Response& resp)->void{resp.set_content("hello world","text/plain");resp.status=200;});//请求方法为GET,请求内容为纯文本,(\d+)为正则表达式,\d表示匹配一个数字,+表示重复匹配一个或多个,()表示捕获数字sr.Get(R"(/numbers/(\d+))",[&](const httplib::Request& req,httplib::Response& resp)->void{//下标为0的内容是这个path,后面捕获的内容auto number=req.matches[1];  //设置响应内容resp.set_content(number,"text/plain");//设置状态码 resp.status=200;});//请求方法是post,请求内容是文件请求sr.Post("/multipart",[&](const httplib::Request& req,httplib::Response& resp)->void{//获取文件个数size_t size=req.files.size();//判断是否存在指定文件bool ret=req.has_file("name1");if(ret){std::cout<<"文件获取失败"<<std::endl;}//获取文件内容httplib::MultipartFormData file= req.get_file_value("name1");//设置响应主体为文件内容resp.body=file.filename;resp.body="\n";resp.body=file.content;//设置报头resp.set_header("Content-Type","text/plain");//状态码resp.status=200;});sr.listen("0.0.0.0",9999);return 0;
}

5.简单客户端搭建

#include"httplib.h"int main()
{//启动客户端httplib::Client cl("127.0.0.1",9999);//发送请求httplib::Result res=cl.Get("/hi");std::cout<<res->status<<std::endl;std::cout<<res->body<<std::endl;httplib::Result res=cl.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" },};httplib::Result res=cl.Post("/upload",items);std::cout<<res->status<<std::endl;std::cout<<res->body<<std::endl;return 0;
}

四、文件使用工具类设计

1.类的功能结构

成员变量:文件名
成员函数:获取文件大小,获取文件最后修改时间,获取文件最后访问时间,获取文件名,设置文件内容,获取文件内容,获取文件指定位置的指定长度的内容,获取指定目录中所有文件信息,判断文件是否存在,创建目录,文件压缩,文件解压缩;

2.功能实现

#include <iostream>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <cstring>
#include <fstream>
#include"bundle.h"namespace cloud
{class FileUtil{public:// 获取文件名bool Getfilename(const std::string &path){// 从后往前查找第一个'/'size_t pos = path.find_last_of("/");if (pos == std::string::npos){std::cout << "get file name fail" << std::endl;return false;}// 提取文件名_filename = path.substr(pos + 1);return true;}// 获取文件大小size_t GetFileSize(){// 填充文件属性结构体statstruct stat sta;int ret = stat(_filename.c_str(), &sta);// 判断填充是否成功if (ret == -1){std::cout << "get file access time fail,error code: " << errno << "error info: " << strerror(errno) << std::endl;return -1;}// 提取文件最后访问时间属性return sta.st_size;}// 获取文件最后一次访问时间time_t GetFileATime(){// 填充文件属性结构体statstruct stat sta;int ret = stat(_filename.c_str(), &sta);// 判断填充是否成功if (ret == -1){std::cout << "get file access time fail,error code: " << errno << "error info: " << strerror(errno) << std::endl;return -1;}// 提取文件最后访问时间属性return sta.st_atime;}// 获取文件最后一次修改时间time_t GetFileMTime(){// 填充文件属性结构体statstruct stat sta;int ret = stat(_filename.c_str(), &sta);// 判断填充是否成功if (ret == -1){std::cout << "get file access time fail,error code: " << errno << "error info: " << strerror(errno) << std::endl;return -1;}// 提取文件最后修改时间属性return sta.st_mtime;}// 设置文件内容bool SetFileContent(const std::string &Content){// 以写方式打开文件std::ofstream ofs;ofs.open(_filename, std::ios::binary);// 写文件ofs.write(Content.c_str(), Content.size());// 判断流是否错误if (!ofs.good()){std::cout << "file write" << _filename << "fail" << std::endl;return false;}ofs.close();return true;}// 获取文件指定位置指定长度内容bool GetFileSpecifiedContent(std::string &content, const int pos, const int len){// 以读方式打开文件std::ifstream ifs(_filename, std::ios::binary);// 设置文件下一个字符访问位置ifs.seekg(pos, std::ios::beg);// 设置接收缓冲区大小char buffer[len];// 读取文件ifs.read(buffer, len);// 判断流是否错误if (!ifs.good()){std::cout << "file write specified content " << _filename << "fail" << std::endl;return false;}// 提取文件内容content = buffer;ifs.close();return true;}// 获取文件内容bool GetFileContent(std::string &content){size_t size=this->GetFileSize();return GetFileSpecifiedContent(content,0,size);}// 文件压缩bool Compress(const std::string &Comfilename){std::string content;if(!GetFileContent(content)){return false;}// 获取压缩文件内容std::string package = bundle::pack(bundle::LZIP, content);// 以写方式打开压缩内容存储文件std::ofstream ofs(Comfilename, std::ios::binary);// 写文件ofs.write(package.c_str(), package.size());// 判断流是否错误if (!ofs.good()){std::cout << "file" << _filename << "compress fail" << std::endl;return false;}// 关闭文件ofs.close();return true;}// 文件解压缩bool Uncompress(const std::string uncomfilename){std::string content;if(!GetFileContent(content)){return false;}// 解压内容std::string package=bundle::unpack(content);// 打开解压内容存储文件std::ofstream ofs(uncomfilename, std::ios::binary);// 写入文件ofs.write(&package[0], package.size());// 判断流是否错误if (!ofs.good()){std::cout << "file" << _filename << "uncompress fail" << std::endl;return false;}// 关闭文件ofs.close();return true;}public:std::string _filename;};
}

3.常见压缩算法

RLE(run length encoding 运行长度编码)
将连续的重复字符替换为字符+重复次数进行压缩.
HUFFMAN(哈佛曼算法)算法
1.统计字符出现频率
2.将字符用二进制从0,01,11,等开始依次编码,编码顺序由字符出现频率从高到低开始编码.
3.构建哈夫曼二叉树,字符和频率作为节点创建最小堆,取出频率最小的两个堆将他们将的字符合并,频率相加组成新节点插入堆中,依次归并,最终得到所以字符频率之和的新节点结束.
4.从根节点遍历哈夫曼树,路径往左标记0,往右标记1,遍历到叶子节点,记录路径上所有标记形成编码,将叶子节点的字符与编码组成映射关系记录到表中
5.将对应的字符转化编码进行压缩.
DEFLATE算法
1.使用RLE算法去重
2.使用哈夫曼算法堆字符编码

五、json工具类实现

#pragma once
#include<sstream>
#include<iostream>
#include<jsoncpp/json/json.h>
#include<memory>namespace cloud
{class JsonUtil{public://序列化static bool Serialize(const Json::Value& root,std::string* str){Json::StreamWriterBuilder swb;std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());std::ostringstream oss;sw->write(root,&oss);*str=oss.str();return true;}//反序列化static bool Deserialize(Json::Value* root,const std::string& str){Json::CharReaderBuilder crb;std::string err;std::unique_ptr<Json::CharReader> cr(crb.newCharReader());bool ret=cr->parse(&str[0],&str[0]+str.size(),root,&err);if(!ret){std::cout<<err<<std::endl;return false;}return true;}};
}

六、服务端配置信息模块

1.设计思想

程序各个模块需要使用到热点判断时间,文件下载前缀名,文件压缩包后缀名,上传文件存放路径,文件压缩存放路径,备份信息存储文件,IP 地址,端口这个样的固定的关键信息,使用一个类统一管理这些信息,方便管理,便于后续维护.又因为需要不止一个模块,需要访问这些信息,而且使用的时机也不同,所以需要这个类是一个全局唯一对象,单例模式的类管理信息,用文件存储配置信息.在创建对象的时候读取配置文件,将最新的配置信息加载到程序中,使程序每次启动使用的是最新的信息.

2.实现

#pragma once
#include "fileutil.hpp"
#include "json.hpp"
#include <mutex>#define CNFFILE_NAME "./config.txt"namespace cloud
{class Config{private:// 全局静态对象static Config *cnf;static std::mutex _mutex;public:static Config *Instance(){if (cnf == nullptr){_mutex.lock();if (cnf == nullptr){cnf = new Config;}}_mutex.unlock();return cnf;}Config(){Readfile(CNFFILE_NAME);}bool Readfile(const std::string &filename){// 获取配置文件内容cloud::FileUtil fu(filename);std::string content;fu.GetFileContent(content);// 反序列化文件内容Json::Value root;cloud::JsonUtil::Deserialize(&root, content);_hot_time = root["hot_time"].asInt();_server_ip = root["server_ip"].asString();_server_port = root["server_port"].asInt();_url_prefix = root["url_prefix"].asString();_arc_suffix = root["arc_suffix"].asString();_back_dir = root["back_dir"].asString();_pack_dir = root["pack_dir"].asString();_manager_file = root["manager_file"].asString();}public:// 热点中断时间time_t _hot_time;// 文件下载URL前缀路径std::string _url_prefix;// 压缩包后缀名称std::string _arc_suffix;// 上传文件存放路径std::string _back_dir;// 压缩文件存放路径std::string _pack_dir;// 服务端备份信息存放文件std::string _manager_file;// 服务器访问 IP 地址std::string _server_ip;// 服务器访问端口uint16_t _server_port;};Config* Config::cnf = nullptr;std::mutex Config::_mutex;}

七、服务端数据管理模块

1.设计思想

对客户端上传的文件进行热点管理,将长时间没有被访问的文件进行压缩存储,需要用到文件的服务器本地存储路径,服务器本地压缩路径,文件最后一次访问时间,文件最后一次修改时间,文件压缩标志;客户端下载文件,需要用到文件索引信息URL,浏览备份文件列表,需要用向用户展示文件大小.为了方便维护管理,所以需要用一个单独的模块封装设计.

**功能设计:**文件上传涉及到信息插入,文件列表展示以及热点管理涉及到信息查询,所以类功能设计主要是插入和查询功能.如果一直有用户的上传的文件需要维护,文件的信息就一直需要维护,所以考虑用文件实现信息的持久化存储,每次数据更新就将数据持久化存储.服务器启动时需要读取加载文件信息到程序中,所以还需要一个初始化模块,用户加载文件信息到程序中.以供使用.

管理结构:考虑到热点管理模块对与查询文件信息的操作比较频繁,用哈希表结构管理文件信息,提高查询效率.

数据会被服务器,热点管理多个线程访问,服务器模块本身使用的是一个线程池,处理应用程序发来的请求,请求中有查询和插入操作,热点管理只是查询操作,所以对于数据的访问是读多写少,使用读写锁加锁可以提高并发性能.

2.实现

#pragma once
#include "fileutil.hpp"
#include "config.hpp"
#include "json.hpp"
#include <unistd.h>
#include <unordered_map>
namespace cloud
{struct BackUpInfo{// 文件最后一次访问时间Json::UInt64 last_a_time = 0;// 文件最后一次修改时间Json::UInt64 last_m_time = 0;// 文件大小Json::UInt64 file_size = 0;// 文件压缩标志bool pack_flag = false;// 文件实际存储路径,backdirstd::string real_path;// 文件压缩存储路径 packdirstd::string pack_path;// 用户请求std::string url;// 填充结构信息bool NewBackUpInfo(const std::string &realpath){// 获取文件名FileUtil fu(realpath);// 如果文件不存在就不处理if (fu.Exists() == false){std::cout << "file not exists" << std::endl;return false;}// 填充文件大小file_size = fu.GetFileSize();// 填充文件最后访问时间last_a_time = fu.GetFileATime();// 填充文件最后修改时间last_m_time = fu.GetFileMTime();// 填充文件实际存储路径real_path = realpath;// 填充文件压缩存储路径pack_path = Config::Instance()->GetPackDir() + fu.FileName() + Config::Instance()->GetPackSuffix();// 用户请求路径url = Config::Instance()->GetDownLoadPreFix() + fu.FileName();// 填充文件压缩标志pack_flag = false;}};// 数据管理类class InfoManager{public:// 构造InfoManager(){// 初始化读写锁pthread_rwlock_init(&_rwlock, nullptr);// 获取上传文件信息管理文件_backup_file = Config::Instance()->GetBackUpFile();// 初始化加载InitLoad();}// 初始化加载void InitLoad(){// 读文件std::string str;_backup_file.GetFileContent(str);// 反序列化Json::Value root;JsonUtil::Deserialize(&root, str);// 填充BackUpInfo结构体信息字段for (Json::Value::ArrayIndex i = 0; i < root.size(); i++){BackUpInfo info;info.file_size = root[i]["file_size"].asUInt64();info.last_a_time = root[i]["last_a_time"].asUInt64();info.last_m_time = root[i]["last_m_time"].asUInt64();info.real_path = root[i]["real_path"].asString();info.pack_flag = root[i]["pack_flag"].asBool();info.pack_path = root[i]["pack_path"].asString();info.url = root[i]["url"].asString();// 将BackUpInfo对象插入哈希表中Insert(info);}}// 持久存储bool Storge(){std::vector<BackUpInfo> arr;if (!GetAll(&arr)){std::cout<<"get all file backup info fail"<<std::endl;return false;}Json::Value root;for (size_t i = 0; i < arr.size(); i++){// 添加到Json::ValueJson::Value item;item["file_size"] = arr[i].file_size;item["last_a_time"] = arr[i].last_a_time;item["last_m_time"] = arr[i].last_m_time;item["real_path"] = arr[i].real_path;item["pack_flag"] = arr[i].pack_flag;item["pack_path"] = arr[i].pack_path;item["url"] = arr[i].url;root.append(item);// 序列化std::string str;JsonUtil::Serialize(root, &str);// 将信息写入到上传文件信息备份文件中_backup_file.SetFileContent(str);}return true;}// 向table插入数据bool Insert(const BackUpInfo &info){// 加锁pthread_rwlock_wrlock(&_rwlock);// 插入数据_table.insert(std::make_pair(info.url, info));// 解锁pthread_rwlock_unlock(&_rwlock);//持久化存储Storge();return true;}// 更新数据bool Updata(const BackUpInfo &info){// 加锁pthread_rwlock_wrlock(&_rwlock);// 插入数据_table.insert(std::make_pair(info.url, info));// 解锁pthread_rwlock_unlock(&_rwlock);//持久化存储Storge();return true;}// 通过url获取一个备份文件数据bool GetOneByUrl(const std::string &url, BackUpInfo *info){// 加锁pthread_rwlock_wrlock(&_rwlock);// 查找if (_table.count(url) == false){std::cout << "not find file info" << std::endl;return false;}// 提取数据*info = _table[url];// 解锁pthread_rwlock_unlock(&_rwlock);return true;}// 通过实际存储路径获取一个备份文件数据bool GetOneByRealPath(const std::string &realpath, BackUpInfo *info){// 加锁pthread_rwlock_wrlock(&_rwlock);// 查找for (auto it = _table.begin(); it != _table.end(); it++){if (it->second.real_path == realpath){*info = it->second;return true;}}pthread_rwlock_unlock(&_rwlock);// 解锁std::cout << "not find file info" << std::endl;return false;}// 获取全部信息bool GetAll(std::vector<BackUpInfo> *arr){// 加锁pthread_rwlock_wrlock(&_rwlock);// 遍历获取数据for (auto e : _table){arr->push_back(e.second);}// 解锁pthread_rwlock_unlock(&_rwlock);return true;}~InfoManager(){pthread_rwlock_destroy(&_rwlock);}private:// 文件操作工具FileUtil _backup_file;// 读写锁pthread_rwlock_t _rwlock;// hash表std::unordered_map<std::string, BackUpInfo> _table;};
}

八、服务端热点管理模块

1.设计思想

对于客户端上传的文件,实时检测文件是否被经常访问,对于不经常被访问的文件,压缩存储可以减少服务器磁盘压力.

热点对象访问全局数据对象获取所有备份文件信息,遍历所有文件信息,利用数据中的文件访问时间与当前时间作比较,判断该文件是否为热点文件,将非热点文件压缩存储在新的路径下,删除源文件可以避免源文件被重复检测.

2.代码

#pragma once
#include "DataManager.hpp"
#include "config.hpp"
#include "fileutil.hpp"extern cloud::InfoManager *_data;
namespace cloud
{class HotManager{public:HotManager(){// 从配置文件获取成员信息_backdir = Config::Instance()->GetBackDir();_packdir = Config::Instance()->GetPackDir();_hottime = Config::Instance()->GetHotTime();_pack_suffix = Config::Instance()->GetPackSuffix();// 目录不存在就创建目录FileUtil(_backdir).CreateDirectory();FileUtil(_packdir).CreateDirectory();}bool HotJudge(const std::string &filename){// 获取最近访问时间与当前时间time_t last_atime = FileUtil(filename).GetFileATime();time_t cur_time = time(NULL);// 非热点文件if (cur_time - last_atime > _hottime){return true;}// 热点文件return false;}void RunModule(){while (1){// 遍历目录获取文件名std::vector<std::string> array;FileUtil(_backdir).ScanDirectory(&array);// 判断是否为热点文件for (auto a : array){// 热点文件不处理if (HotJudge(a) == false){continue;}// 获取文件备份信息BackUpInfo info;if (_data->GetOneByRealPath(a, &info) == false){info.NewBackUpInfo(a);}// 压缩非热点文件7// 删除源文件FileUtil fu(a);fu.Compress(info.pack_path);fu.Remove();// 修改备份文件信息info.pack_flag = true;_data->Updata(info);}}}private:// 备份文件信息管理类对象std::string _backdir;std::string _packdir;std::string _pack_suffix;int _hottime;};
}

九、服务端业务处理模块

1.实现思想

服务端要能响应客户端的文件列表查看请求,文件下载请求,文件上传请求.

文件上传功能的实现:判断响应报文中是否设置了"file"(自定义头部键值),使用request对象获取文件内容,将内容写入上传目录中,更新全局数据对象结构中的数据.

断点续传功能的实现原理:
正常文件下载,客户端请求POST /download/filename HTTP/1.1+请求报头+主体;
服务端响应:HTTP/1.1 200 OK+请求报头(“Etag: asasdfjashdfoa(文件唯一标识)”,“Accept-Range: bytes”(告诉客户端服务端支持断点续传))+body(文件内容);

异常中断后,客户端请求断点续传,POST /download/filename HTTP/1.1+请求报头(Range: bytes=10-900)(If-Range: 服务器响应的唯一标识)+body;

首先服务端检测是否有If-Range头部字段,判断是否需要断点续传,在判断If-Range对应的etag文件唯一标识符是否与当前文件的唯一标识符一致,不一致说明文件在此之前已经被修改了,需要重新下载文件,否则就获取Range请求报头的区间值,Range值设置方式有Range: bytes 10-100或者Range: bytes 10-表示10-文件末尾,将区间内的文件内容设置到body中响应给客户端

服务器响应: HTTP/1.1 206 partial content+请求报头(“Etag: asasdfjashdfoa(文件唯一标识)”,“Accept-Range: bytes”(告诉客户端服务端支持断点续传),“Content-Range: bytes 10-900/891”)+body(文件内容);

2.代码

#pragma once
#include "httplib.h"
#include "config.hpp"
#include "DataManager.hpp"
#include <sstream>
#include <ctime>extern cloud::InfoManager *_data;namespace cloud
{class Service{public:Service(){_Download_prefix = Config::Instance()->GetDownLoadPreFix();_server_ip = Config::Instance()->GetServerIp();_server_port = Config::Instance()->GetServerPort();}// 上传static void UpLoad(const httplib::Request &req, httplib::Response &resp){// 判断有没有文件上传区域bool flag = req.has_file("file");if (flag == false){std::cout << "not file contant" << std::endl;resp.status = 400;return;}// 获取文件信息auto file = req.get_file_value("file");// 获取文件上传路径std::string backdir = Config::Instance()->GetBackDir();// 获取文件实际存储路径std::string realpath = backdir + FileUtil(file.filename).FileName();// 向文件写入数据FileUtil(realpath).SetFileContent(file.content);// 向数据管理模块添加信息BackUpInfo info;info.NewBackUpInfo(realpath);_data->Insert(info);}static std::string GetETag(const BackUpInfo &info){FileUtil fu(info.real_path);std::string etag = fu.FileName();etag += '-';etag += std::to_string(info.file_size);etag += '-';etag += std::to_string(info.last_m_time);return etag;}// 下载static void Download(const httplib::Request &req, httplib::Response &resp){// 根据url获取文件备份信息BackUpInfo info;_data->GetOneByUrl(req.path, &info);// 判断文件是否被压缩,如果被压缩,要先解压缩if (info.pack_flag == true){// 将压缩包解压到上传路径下FileUtil fu(info.pack_path);fu.Uncompress(info.real_path);// 删除压缩包fu.Remove();// 修改备份信息info.pack_flag = false;// 更新备份信息_data->Updata(info);}bool resume_flag = false;// 判断请求字段中是否有需要热点续传if (req.has_header("If-Range")){auto old_etag = req.get_header_value("If-Range");if (old_etag == GetETag(info)){resume_flag = true;}}// 读取文件信息将内容放入响应内容FileUtil(info.real_path).GetFileContent(resp.body);if (resume_flag){// 设置头部字段resp.set_header("Accept-Ranges", "bytes");// 热点续传标志resp.set_header("ETag", GetETag(info));// 响应文件类型resp.set_header("Content-Type", "application/octet-stream");resp.status = 206;}else{// 设置头部字段resp.set_header("Accept-Ranges", "bytes");// 热点续传标志resp.set_header("ETag", GetETag(info));// 响应文件类型resp.set_header("Content-Type", "application/octet-stream");resp.status = 200;}}static const char *Gettime(time_t time){return std::ctime(&time);}// 列表显示static void ListShow(const httplib::Request &req, httplib::Response &resp){(void)req;// 获取所有文件信息std::vector<BackUpInfo> array;_data->GetAll(&array);std::stringstream ss;ss << "<html><head><title>Download</title></head>";ss << "<body><h1>Download</h1><table>";ss << "<meta charset='UTF-8'>";for (auto a : array){ss << "<tr>";FileUtil fu(a.real_path);ss << "<td><a href='" << a.url << "'>" << fu.FileName() << "</a></td>";ss << "<td align='right'>" << Gettime(a.last_m_time) << "</td>";ss << "<td align='right'>" << a.file_size / 1024 << "k </td>";ss << "</tr>";}ss << "</table></body></html>";// 设置响应展示页面resp.body = ss.str();resp.set_header("Content-Type", "text/html");resp.status = 200;}// 运行void RunModule(){// 创建服务器// 接收上传请求_server.Post("/upload", UpLoad);// 接收下载请求std::string str = Config::Instance()->GetDownLoadPreFix() + "(.*)";_server.Get(str.c_str(), Download);// 接收列表展示请求_server.Get("/listshow", ListShow);_server.Get("/", ListShow);// 启动服务器_server.listen(_server_ip.c_str(), _server_port);}private:std::string _server_ip;uint16_t _server_port;std::string _Download_prefix;httplib::Server _server;};
}

十、客户端数据模块

1.实现思想

客户端要实现的功能是对指定文件夹中的文件自动进行备份上传。但是并不是所有的文件每次都需要上传,需要判断哪些文件需要上传哪些不需要,因此需要将备份的文件信息给管理起来,作为下一次文件是否需要备份的判断。因此需要被管理的信息包含以下:文件路径名称,文件唯一标识:由文件名,最后一次修改时间,文件大小组成的一串信息

2.代码

#pragma once
#include<sstream>
#include<string>
#include<unordered_map>
#include"fileutil.hpp"namespace cloud
{class DataManager{public:DataManager(const std::string& back_file):_back_file(back_file){InitLoad();}//持久化存储bool Storage(){std::stringstream ss;//遍历获取表中的信息for (auto a : _table){ss << a.first << " " << a.second << "\n";}//向管理信息文件写入信息FileUtil fu(_back_file);fu.SetFileContent(ss.str());return true;}bool Split(const std::string& str, const std::string& sep, std::vector<std::string>* array){//分隔符位置,开始查找位置size_t pos = 0, indx = 0;while (1){//从indx位置查找pos=str.find(sep, indx);//没找到if (pos == std::string::npos){return false;}//将string转化为stringstream插入arraystd::string tmp = str.substr(indx, pos - indx);array->push_back(tmp);//更新下次查找开始位置indx = pos + sep.size();}if (indx < str.size()){array->push_back(str.substr(indx));}return true;}//初始化加载bool InitLoad(){//读取文件信息std::string str;FileUtil fu(_back_file);fu.GetFileContent(str);//按照格式分割文件内容std::vector<std::string> array;Split(str, "\n", &array);//将格式化信息插入表中for (auto a : array){std::stringstream ss(a);std::string filename, flag;ss >> filename>>flag;_table.insert(std::make_pair(filename, flag));}return true;}//插入数据bool Insert(const std::string& key, const std::string& value){_table[key] = value;Storage();return true;}//更新数据bool UpDate(const std::string& key, const std::string& value){_table[key] = value;Storage();return true;}//通过key值获取一个数据bool GetOneByKey(const std::string& key, std::string* value){auto it = _table.count(key);if (it == false){return false;}*value = _table[key];return true;}private://数据管理表std::unordered_map<std::string, std::string> _table;//持久化存储文件std::string _back_file;};
}

十一、客户端业务处理模块

循环检测指定目录中的文件是否需要将上传,被更改的,或者是没有上床过的文件都需要上传,上传的同时将文件路径名作为key值,文件名+文件大小+最后一次修改时间组成的文件唯一标识符作为value存储在哈希表中,并持久化存储.

#pragma once
#include"data.h"
#include"httplib.h"
#include<windows.h>
#define BACKDIR "./backdir/"
#define BACKFILE "backfile.dat"
#define SERVER_IP "170.106.107.74"
#define SERVER_PORT 9090
namespace cloud
{class BackUp{public:BackUp(const std::string& back_dir= BACKDIR) :_back_dir(back_dir){_data = new DataManager(BACKFILE);}std::string GetFileIdentifier(const std::string& filename){FileUtil fu(filename);std::string size = std::to_string(fu.GetFileSize());std::string mtime = std::to_string(fu.GetFileMTime());std::stringstream ss;ss << filename << "-" << size << "-" << mtime;return ss.str();}//判断是否需要上传bool IsUpLoad(const std::string& filename){FileUtil fu(filename);std::string id;bool ret=_data->GetOneByKey(filename, &id);if ((ret&& GetFileIdentifier(filename) == id)|| (time(NULL) - fu.GetFileMTime() < 3)){return false;}return true;}//上传bool UpLoad(const std::string& filename){//获取文件内容FileUtil fu(filename);std::string body;fu.GetFileContent(body);//创建Client对象httplib::Client client(SERVER_IP, SERVER_PORT);//设置文件上传格式httplib::MultipartFormData item;item.filename = fu.FileName();item.content = body;item.content_type = "application/octet-stream";item.name = "file";httplib::MultipartFormDataItems items;items.push_back(item);//发送上传请求auto ret=client.Post("/upload", items);//判断是否成功if (!ret || ret->status != 200){return false;}return true;}void RunModule(){while (1){//遍历获取目录文件名称FileUtil fu(_back_dir);std::vector<std::string> array;fu.ScanDirectory(&array);for (auto a : array){//判断文件是否需要上传if (IsUpLoad(a) == true){//文件上传成功就更新数据if (UpLoad(a) == true){_data->Insert(a, GetFileIdentifier(a));}}}Sleep(1);}}private:std::string _back_dir;DataManager* _data;};
}

十二、项目类关系图

在这里插入图片描述

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

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

相关文章

3. Windows下C++/MFC调用hiredis库操作redis示例

一、头文件目录 将之前下载和编译好的Redis目录拷贝到新建好的工程目录下面&#xff0c;再点击测试工程的右键/属性&#xff0c;点击C/常规&#xff0c;附加包含目录添加以下路径&#xff0c;注意如果原先有多个路径&#xff0c;在末尾处添加分号后再粘贴&#xff1a; 点击C/常…

树莓派玩转openwrt软路由:11.OpenWrt安装NodeRed

1、更新软件源 opkg update2、安装nodered docker run -it -p 1880:1880 --name mynodered nodered/node-red3、安装完整性测试 实现一个打印hello world的demo&#xff0c;每隔1秒打印一次

css 星星闪烁加载框

今天带来的是普灵普灵的loader闪烁加载框 效果如下 开源精神给我们带来了源码 ,源码如下 <!DOCTYPE html> <html lang"en"><head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width, in…

Spring framework Day10:JSR330注入注解

前言 JSR330是Java社区标准化进程&#xff08;Java Community Process&#xff0c;简称JCP&#xff09;中的一个规范&#xff0c;全名为"Dependency Injection for Java"&#xff0c;即Java的依赖注入规范。它定义了一组注解和相关的规范&#xff0c;用于实现依赖注…

python每日一练(7)

&#x1f308;write in front&#x1f308; &#x1f9f8;大家好&#xff0c;我是Aileen&#x1f9f8;.希望你看完之后&#xff0c;能对你有所帮助&#xff0c;不足请指正&#xff01;共同学习交流. &#x1f194;本文由Aileen_0v0&#x1f9f8; 原创 CSDN首发&#x1f412; 如…

Visual Studio 错误CS0006:未能找到元数据文件踩坑记录

前言 在写项目的时候&#xff0c;添加了个新的Nuget包&#xff0c;突然就不行&#xff0c;然后就是报错&#xff0c;找不到文件、 出现的原因是因为项目之间互相引用出现了问题&#xff0c;比如如下情况 先版本回退 如果有Git仓库 第一时间去看Git 文件比较&#xff0c;找到…

YOLOv5算法改进(11)— 主干网络介绍(MobileNetV3、ShuffleNetV2和GhostNet)

前言:Hello大家好,我是小哥谈。主干网络通常指的是深度学习中的主干模型,通常由多个卷积层和池化层组成,用于提取输入数据的特征。在训练过程中,主干网络的参数会被不断优化以提高模型的准确性。YOLOv5算法中的主干网络可以有多种替换方案,为了后面讲解的方便,本篇文章就…

自动驾驶学习笔记(三)——场景设计

#Apollo开发者# 学习课程的传送门如下&#xff0c;当您也准备学习自动驾驶时&#xff0c;可以和我一同前往&#xff1a; 《自动驾驶新人之旅》免费课程—> 传送门 《2023星火培训【感知专项营】》免费课程—>传送门 文章目录 前言 场景设计平台 场景地图 场景基本…

【NLTK系列01】:nltk库介绍

一、说明 NLTK是个啥&#xff1f;它是个复杂的应用库&#xff0c;可以实现基本预料库操作&#xff0c;比如&#xff0c;、将文章分词成独立token&#xff0c;等操作。从词统计、标记化、词干提取、词性标记&#xff0c;停用词收集&#xff0c;包括语义索引和依赖关系解析等。 …

Android---java线程优化 偏向锁、轻量级锁和重量级锁

java 中的线程是映射到操作系统原生线程之上的&#xff0c;如果要阻塞或唤醒一个线程就需要操作系统的帮忙&#xff0c;这就需要从用户态转换到核心态。状态转换需要花费很多时间&#xff0c;如下代码所示&#xff1a; private Object lock new Object();private int value;p…

异星工场入门笔记-01

两年前玩过一点&#xff0c;不看教程&#xff0c;单纯地开放世界自己探索&#xff0c;没有同类游戏经验&#xff0c;因此很难有获得感所以放弃了。现在正版游戏涨到130&#xff0c;看在逆势上涨的份上&#xff0c;我倒想继续探索下这个游戏的价值。 玩魔方&#xff0c;记教程步…

Kubernetes使用OkHttp客户端进行网络负载均衡

在一次内部Java服务审计中&#xff0c;我们发现一些请求没有在Kubernetes&#xff08;K8s&#xff09;网络上正确地实现负载均衡。导致我们深入研究的问题是HTTP 5xx错误率的急剧上升&#xff0c;由于CPU使用率非常高&#xff0c;垃圾收集事件的数量很多以及超时&#xff0c;但…

【UE 插件】UE4 虚幻引擎 插件开发(带源码插件打包、无源码插件打包) 有这一篇文章就够了!!!

目录 0 引言1 快速入门1.1 新建插件的前提1.2 创建插件步骤1.3 打包插件 2 无源代码的插件制作3 插件详细介绍3.1 插件的使用方法3.1 UE 预置插件模版3.1.1 空白3.1.2 纯内容3.1.3 编辑器独立窗口3.1.4 编辑器工具栏按钮3.1.5 编辑器模式3.1.6 第三方库3.1.7 蓝图库 3.2 插件中…

华为OD机试 - 最大括号深度 - 栈stack(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明华为OD机试 2023B卷题库疯狂收录中,刷题点这里 专栏导读 本专栏收录于《华为OD机试(JAVA)真题(A卷+B卷)》。 刷的越多,

本、硕、博区别真的辣么大吗?

61&#xff1a; 发际线已经说明了一切…… Super Mario&#xff1a; 小学&#xff0c;老师告诉学生&#xff1a;“森林里有只老虎&#xff0c;已经被我关在笼子里&#xff0c;我会带你去那个地方&#xff0c;然后给你一把猎枪&#xff0c;告诉你猎枪怎么用&#xff0c;并开枪…

搭建一个vscode+uni+vue的小程序项目

我们使用 vue2 创建工程作为示例&#xff0c;uni-app中Vue2版的组件库和插件也比较多&#xff0c;稳定、问题少&#xff0c;可以先参考下官方文档:uni-app官网 既然是使用vue脚手架&#xff0c;那肯定要全局安装vue/cli&#xff0c;已安装的可以跳过。 注意&#xff1a;Vue2创…

day05-前后端项目上传到gitee、后端多方式登录接口、发送短信功能、发送短信封装、短信验证码接口、短信登录接口

1 前后端项目上传到gitee 2 后端多方式登录接口 2.1 序列化类 2.2 视图类 2.3 路由 3 发送短信功能 4 发送短信封装 4.0 目录结构 4.1 settings.py 4.2 sms.py 5 短信验证码接口 6 短信登录接口 6.1 视图类 6.2 序列化类 1 前后端项目上传到gitee # 我们看到好多开源项目…

解决react使用css module无法重写bootstrap样式的问题

react使用css module虽然能够解决样式污染&#xff0c;但是同时也失去了写css样式的灵活性&#xff0c;特别是&#xff1a;在.module.css文件中当子元素是非变量的静态class类&#xff08;比如bootstrap&#xff09;, 此时使用css选择器对该子元素的样式不会起作用的 比如下面…

boot分页

List<ElectricDispatchTodoPO> todoList electricDispatchTodoService.queryTodlList(vo, sysStaffVO);// 计算总记录数int total todoList.size();// 如果总记录数大于0PageInfo<ElectricDispatchTodoPO> pageInfo new PageInfo<>();if (total > 0) {…

聚观早报 | “百度世界2023”即将举办;2024款岚图梦想家上市

【聚观365】10月13日消息 “百度世界2023”即将举办 2024款岚图梦想家上市 腾势D9用户超10万 华为发布新一代GigaGreen Radio OpenAI拟进行重大更新 “百度世界2023”即将举办 “百度世界2023”将于10月17日在北京首钢园举办。届时&#xff0c;百度创始人、董事长兼首席执…