云备份实战项目

文章目录

  • 前言
  • 一、整体项目简介
  • 二、服务端环境及功能简介
  • 三、 客户端环境及功能简介
  • 四、服务端文件管理类的实现
    • 1. 获取文件大小,最后一次修改时间,最后一次访问时间,文件名称,以及文件内容的读写等功能
    • 2. 判断文件是否存在,创建目录,浏览目录等功能
    • 3. 对文件进行压缩和解压缩处理
  • 五、服务端Json序列化工具的实现
  • 六、 服务端读取配置文件类的实现
  • 七、服务端构建备份信息的类的实现
  • 八、服务端数据管理类的实现
  • 九、服务端热点管理的实现
  • 十、服务端网络通信及业务处理实现
  • 十一、客户端数据管理
  • 十二、客户端网络通信及备份请求
  • 总结


前言

项目主要是是将客户端的文件备份到服务端,并且在服务端合理进行压缩处理,节省空间。并且服务端还支持通过浏览器请求页面展示, 来展示服务端备份的文件,同时还支持下载请求,将服务端备份的文件下载到客户端, 若中途服务器停止运行,还支持断点续传


一、整体项目简介

在这里插入图片描述

二、服务端环境及功能简介

  • 服务端环境 Linux Ubuntu 18.04
  • 具体功能包括 文件管理,Json序列化和反序列化, 读取配置文件, 备份信息, 数据管理, 热点管理,网络通信等
  • 项目再编写服务端代码是,使用vscode远程链接Linux服务器,方便编码。
    在这里插入图片描述

三、 客户端环境及功能简介

  • 客户端环境 Windows vs2022
  • 具体功能包括 文件管理, 数据管理, 客户端网络通信及备份信息等

在这里插入图片描述

四、服务端文件管理类的实现

在这里插入图片描述

1. 获取文件大小,最后一次修改时间,最后一次访问时间,文件名称,以及文件内容的读写等功能

  • 目的, 文件管理类主要是便于后续(数据管理,热点管理等)对文件的操作
    1. 使用C语言的系统调用函数获取文件的大小,最后一次修改时间,最后一次访问时间
    1. 通过string的查找成员函数,找到路径分隔符,截取文件名称
    1. 通过ofstream将字符串设置为文件内容
    1. 通过ifstream获取文件指定位置,指定长度的内容,并复用获取整个文件内容
namespace hhbcloud
{// 简化命名空间namespace fs = std::experimental::filesystem;class FileUtil{public:FileUtil(const std::string& filename):_filename(filename){}bool Remove(){if (remove(_filename.c_str()) == 0){return true;}return false;}// 获取文件大小size_t FileSize(){struct stat st; // 使用stat来获取文件的各种信息if (stat(_filename.c_str(), &st) < 0) // stat函数成功返回0,失败返回-1{std::cout << "get file size error" << std::endl;return -1;}return st.st_size;}// 获取文件最后一次修改时间time_t LastMTime(){struct stat st;if (stat(_filename.c_str(), &st) < 0){std::cout << "get lastmtime error" << std::endl;return -1;}return st.st_mtime;}// 获取文件最后一次访问时间time_t LastATime(){struct stat st;if (stat(_filename.c_str(), &st) < 0){std::cout << "get lastatime error" << std::endl;return -1;}return st.st_atime;}// 获取文件路径中的文件名称std::string FileName(){size_t pos = _filename.find_last_of("/"); // 从后往前找第一个/if (pos == std::string::npos){// 此时没有找到/说明文件路径就是文件名称return _filename;}return _filename.substr(pos + 1);}// 设置文件内容bool SetContent(const std::string& body){// 打开文件std::ofstream ofs;ofs.open(_filename, std::ios::binary); // 以二进制形式打开文件,若文件不存在会自动创建文件if (ofs.is_open() == false){std::cout << "write open file error" << std::endl;return false;}// 打开文件成功后,将body的内容写到文件中ofs.write(&body[0], body.size());if (ofs.good() == false) // 判断文件写入过程中是否出错{std::cout << "write file error" << std::endl;ofs.close();return false;}ofs.close();return true;}// 读取文件指定位置 指定长度的内容, body用来接收获取到的数据bool GetPosLen(std::string* body, size_t pos, size_t len){// 判断 pos + len 是否超出文件大小size_t size = FileSize();if (pos + len > size){std::cout << "pos + len more than file size" << std::endl;return false;}// 读的形式打开文件std::ifstream ifs;ifs.open(_filename.c_str(), std::ios::binary);//二进制形式打开if (ifs.is_open() == false) // 打开失败报错{std::cout << "read open file error" << std::endl;return false;}// 打开成功后,先为body开辟足够的大小body->resize(len); // 避免多次安装// 从文件起始位置定位到pos位置ifs.seekg(pos, std::ios::beg);// 读取内容ifs.read(&(*body)[0], len);if (ifs.good() == false) // 判断读取文件过程是否出错{std::cout << "read file error" << std::endl;ifs.close();return false;}ifs.close();return true;}// 读取整个文件的内容bool GetContent(std::string* body){size_t size = FileSize();return GetPosLen(body, 0, size); // 复用获取文件指定位置指定长度函数}private:std::string _filename;};
}

测试上述功能

void TestFileUtil(const std::string& filename)
{hhbcloud::FileUtil fu(filename);std::cout << fu.FileSize() << std::endl;std::cout << fu.LastMTime() << std::endl;std::cout << fu.LastATime() << std::endl;std::cout << fu.FileName() << std::endl;std::string str = "hello world!!!--";fu.SetContent(str);std::string body;fu.GetContent(&body);std::cout << body << std::endl;hhbcloud::FileUtil nfu("./hello.txt");nfu.SetContent(body);
}int main(int argc, char* argv[])
{if (argc != 2){std::cout << "argv[2]" << std::endl;return -1;}TestFileUtil(argv[1]);return 0;
}

在这里插入图片描述

2. 判断文件是否存在,创建目录,浏览目录等功能

  • 此处功能需要C++17的文件系统, C++14也支持
  • 需要高版本的gcc才能支持
  • 简化命名空间 可以便于使用文件系统

需要包含的头文件, #include<experimental/filesystem>
简化命名空间可以使用: namespace fs = std::experimental::filesystem;

// 判断文件是否存在
bool Exists()
{return fs::exists(_filename); // c++17文件系统的函数判断文件是否存在
}// 创建目录
bool CreateDirectory()
{if (Exists() == true) return true; // 若文件存在则不必创建return fs::create_directories(_filename); // 创建目录时,传入路径,则创建多级目录
}
// 获取指定目录下的所有文件名,并存放到array数组中
bool ScanDirectory(std::vector<std::string>* array)
{// 使用 fs::directory_iterator 函数获取指定目录下的所有文件for (auto& file : fs::directory_iterator(_filename)){// 应该判断获取的文件是否是目录,因为目录下可能还有目录if (fs::is_directory(file) == true) // is_derectory 判断是否是目录{continue; // 如果是目录文件则跳过}// file并不是string类对象,可以使用path函数实例化,relative_path获取相对路径// string带路径的string的文件名array->push_back(fs::path(file).relative_path().string());}
}

测试上述功能

void TestFileUtil(const std::string& filename)
{hhbcloud::FileUtil fu(filename);fu.CreateDirectory();std::vector<std::string> array;fu.ScanDirectory(&array);for (auto& e : array){std::cout << e << std::endl;}std::cout << std::endl;
}

在这里插入图片描述

3. 对文件进行压缩和解压缩处理

  • 下载第三方bundle库, 并将bundle.cpp和bundle.h 移动到当前文件夹下
  • 在编译时,bundle.cpp编译过长,所以将bundle.cpp生成静态库
  • 将bundle
  1. 生成.o文件
g++ -c bundle.cpp -o bundle.o
  1. 依赖bundle.o生成静态库
ar -cr libbundle.a bundle.o
  1. 创建lib目录,将libbundle.a放置lib目录下,完成后可以删除bundle.cpp和bundle.o,编译时需要如下
g++ -o test Test.cpp -Llib -lstdc++fs -lbundle -pthread
  • bundle库内部使用了多线程,所以编译需要注意
// 压缩文件
bool Compress(const std::string& packname)
{// 先将文件读出来std::string body;if (GetContent(&body) == false){std::cout << "compress get file content error" << std::endl;return false;}// 使用pack函数对读出来的内容进行压缩std::string packed = bundle::pack(bundle::LZIP, body); // packed中存储的即为压缩后的数据// 将压缩后的文件写入packname文件中FileUtil fu(packname);if (fu.SetContent(packed) == false){std::cout << "compress set packname content error" << std::endl;return false;}return true;
}// 文件解压缩
bool Uncompress(const std::string& unpackname)
{// 先读取文件内容std::string body;if (GetContent(&body) == false){std::cout << "uncompress get file content error" << std::endl;return false;}// 使用unpack解压缩文件std::string unpacked = bundle::unpack(body); // unpacked存储的是解压缩后的数据// 将解压后的文件写到解压文件中FileUtil fu(unpackname);if (fu.SetContent(unpacked) == false){std::cout << "uncompress set file content error" << std::endl;return false;}return true;}

测试压缩和解压缩功能

void TestFileUtil(const std::string& filename)
{hhbcloud::FileUtil fu(filename);std::string packname = filename + ".lz";fu.Compress(packname);std::string unpackname = "hello.txt";hhbcloud::FileUtil nfu(packname);nfu.Uncompress(unpackname);
}

在这里插入图片描述

五、服务端Json序列化工具的实现

  • json工具本质是对序列化和反序列化进行封装,方便后续操作
  • json只有两个成员函数(序列化和反序列化),使用时不要实例化类对象
  • 所以json的两个成员函数都是静态的。
  • 使用jsoncpp之前先安装jsoncpp, 并且编译时,需要加上 -ljsoncpp
class JsonUtil // json工具目的是封装序列化和反序列化的函数{public:// JsonUtil的序列化和反序列化是一种封装比较单一,不需要创建对象调用成员函数,所以将成员函数设置成静态static bool Serialization(Json::Value& root, std::string* str) // 讲一个结构体序列化为字符串{Json::StreamWriterBuilder swb; // 创建一个 json::StreamWriterBuilder 对象// 使用智能指针管理一个由swb创建的Json::StreamWriter 对象std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());std::stringstream ss; // 创建stringstream,将root序列化后的字符串写入stringstream// 使用sw的write函数将root写入stringstreamif (sw->write(root, &ss) != 0){std::cout << "Serialization write error" << std::endl;return false;}*str = ss.str();return true;}// 反序列化就是将一个字符串,反序列化成一个json结构体static bool Deserialization(const std::string& str, Json::Value* root) {Json::CharReaderBuilder crb; // 创建一个Json::CharReaderBuilder// 使用智能指针管理crb创建的son::CharReader对象std::unique_ptr<Json::CharReader> cr(crb.newCharReader());std::string err; // 用来记录错误信息,cr->parse函数要求必须err参数if (cr->parse(str.c_str(), str.c_str() + str.size(), root, &err) == false){std::cout << "Deserialization parse error" << std::endl;return false;}return true;}};

测试json的功能

void TestJsonUtil()
{// 序列化// 构建一个JSON结构体Json::Value root;root["姓名"] = "小帅";root["年龄"] = 18;float score[3] = { 88.5, 66.7, 99.8 };for (int i = 0;i < 3; i++){root["分数"][i] = score[i];}// 序列化到str中std::string str;hhbcloud::JsonUtil::Serialization(root, &str);std::cout << str << std::endl;// 返序列化Json::Value root1;hhbcloud::JsonUtil::Deserialization(str, &root1);std::cout << root1["姓名"].asString() << std::endl;std::cout << root1["年龄"].asInt() << std::endl;for (int i = 0;i < root1["分数"].size(); i++){std::cout << root1["分数"][i].asFloat() << std::endl;}
}
int main(int argc, char* argv[])
{TestJsonUtil();
}

在这里插入图片描述

六、 服务端读取配置文件类的实现

  • 定义配置文件如下:
    在这里插入图片描述
  • 借助文件管理类,实现读取配置配置文件的类
  • 整个项目只有一个配置文件,配置文件类只需要在使用的时候实例化一份对象
  • 所以将读取配置文件类设计成单例模式的懒汉模式

在这里插入图片描述

namespace hhbcloud
{class Config // 类的作用是获取配置文件中的数据,成员变量即为配置文件中所有的内容{public:// 因为整个系统同一时刻只需要一个读取配置文件的类,因此把他设计成单例模式,并使用懒汉模式提高局部性能static Config* GetInstance() // 不需要创建类对象,所以静态成员函数{if (_instance == nullptr){_mutex.lock(); // 由于此处加锁会极大的影响效率,所以在外部加一层判断,提高效率if (_instance == nullptr) // 此处需要加锁,防止多线程出错{_instance = new Config();}_mutex.unlock();}return _instance;}// 获取热点管理时间int GetHotTime(){return _hot_time;}// 获取服务器监听ipstd::string GetServerIp(){return _server_ip;}// 获取服务器监听portint GetServerPort(){return _server_port;}// 获取下载文件的url前缀std::string GetDownloadPrefix(){return _download_prefix;}// 获取压缩文件的后缀std::string GetPackfileSuffix(){return _packfile_suffix;}// 获取压缩文件的存储路径std::string GetPackDir(){return _pack_dir;}// 获取备份文件的存储路径std::string GetBackDir(){return _back_dir;}// 获取所有文件备份信息的文件std::string GetBackupFile(){return _backup_file;}private:Config(){ReadConfigFile(); // 在创建对象时,调用读取文件信息的函数}bool ReadConfigFile() // 利用文件管理工具和json序列化反序列化工具,将配置文件的内容读到config对象中{FileUtil fu(CONFIG_FILE);std::string body;fu.GetContent(&body); // 获取整个文件的内容// 将字符串反序列化为JSON结构体Json::Value root;JsonUtil::Deserialization(body, &root);_hot_time = root["hot_time"].asInt();_server_ip = root["server_ip"].asString();_server_port = root["server_port"].asInt();_download_prefix = root["download_prefix"].asString();_packfile_suffix = root["packfile_suffix"].asString();_pack_dir = root["pack_dir"].asString();_back_dir = root["back_dir"].asString();_backup_file = root["backup_file"].asString();}private:int _hot_time; // 热点判断时间std::string _server_ip; // 服务器监听ipint _server_port; // 服务器监听端口std::string _download_prefix; // 下载文件的url前缀std::string _packfile_suffix; // 压缩文件的后缀std::string _pack_dir; // 压缩文件的存储路径std::string _back_dir; // 备份文件的存储路径std::string _backup_file; // 备份文件信息的存储文件// 外部使用config时,使用GetInstance函数,因为其是静态,无法访问非静态成员变量,所以将_instance设置为静态static Config* _instance; // 懒汉模式,默认先创建一个指针,需要的时候在实例化对象static std::mutex _mutex; // 懒汉模式在实例化对象时,需要加锁};Config* Config::_instance = nullptr; // 静态成员变量默认在类外定义std::mutex Config::_mutex;
}

测试读取配置文件的类

void TestConfig()
{hhbcloud::Config* config = hhbcloud::Config::GetInstance();std::cout << config->GetHotTime() << std::endl;std::cout << config->GetServerIp() << std::endl;std::cout << config->GetServerPort() << std::endl;std::cout << config->GetDownloadPrefix() << std::endl;std::cout << config->GetPackfileSuffix() << std::endl;std::cout << config->GetPackDir() << std::endl;std::cout << config->GetBackDir() << std::endl;std::cout << config->GetBackupFile() << std::endl;
}
int main(int argc, char* argv[])
{TestConfig();return 0;
}

在这里插入图片描述

七、服务端构建备份信息的类的实现

在这里插入图片描述

struct Backinfo // 定义并获取文件存在在服务器上的备份信息{bool NewBackinfo(const std::string& realpath){FileUtil fu(realpath);if (fu.Exists() == false) // 文件不存在则报错返回{std::cout << "newBackinfo file is not exists" << std::endl;return false;}_pack_flag = false;_fsize = fu.FileSize();_atime = fu.LastATime();_mtime = fu.LastMTime();_real_path = realpath;// config获取配置文件中定义的单例类 Config* config = Config::GetInstance();// 压缩路径应该是 配置文件中的压缩目录 + 文件名称 + 文件压缩后缀_pack_path = config->GetPackDir() + fu.FileName() + config->GetPackfileSuffix();// url下载路径应该是 配置文件中下载前缀 + 文件名称std::string downloadPrefix = config->GetDownloadPrefix();std::string urlpath = downloadPrefix + fu.FileName();_url_path = urlpath;return true;}bool _pack_flag; // 文件是否被压缩标志size_t _fsize; // 文件的大小time_t _atime; // 文件最后一次访问时间time_t _mtime; // 文件最后一次修改时间std::string _real_path; // 文件实际存储路径名称std::string _pack_path; // 亚搜包存储路径名称std::string _url_path; // url下载文件的路径};

测试备份信息功能

void TestDataManager(const std::string& realpath)
{hhbcloud::Backinfo info;if (info.NewBackinfo(realpath) == false){return;}std::cout << info._pack_flag << std::endl;std::cout << info._atime << std::endl;std::cout << info._mtime << std::endl;std::cout << info._fsize << std::endl;std::cout << info._pack_path << std::endl;std::cout << info._real_path << std::endl;std::cout << info._url_path << std::endl;
}

在这里插入图片描述

八、服务端数据管理类的实现

  • 数据管理类是将组织好的文件的备份信息统一存放在备份信息文件中,并且可以从文件中读取备份信息,进行增、改、 查等功能, 并可以重新写回文件的类。
    在这里插入图片描述
 class DataManager{public:DataManager(){// _backup_file 持久化存储文件,就是配置文件中的文件备份信息的文件// 对数据的管理本质就是对服务器备份的文件信息的管理,在进行操作时,需要将当前所有文件信息加载到内存中// 这里用哈希表来存储,提高效率_backup_file = Config::GetInstance()->GetBackupFile(); // 通过config单例类获取// 初始化读写锁pthread_rwlock_init(&_rwlock, nullptr);// 创建对象的同时,初始化加载所有备份信息数据InitLoad();}// 初始化加载 每次创建类都需要先初始化加载所有的数据bool InitLoad(){FileUtil fu(_backup_file);if (fu.Exists() == false){std::cout << "DataManager InitLoad file is not exists" << std::endl;return false;}// 获取备份信息文件中的所有备份信息std::string body;fu.GetContent(&body);// 反序列化Json::Value root;JsonUtil::Deserialization(body, &root);// 通过反序列化得到的json结构体,构建备份信息,插入到哈希表中for (int i = 0;i < root.size(); i++){Backinfo backinfo;backinfo._pack_flag = root[i]["pack_flag"].asBool();backinfo._fsize = root[i]["fsize"].asInt();backinfo._atime = root[i]["atime"].asInt();backinfo._mtime = root[i]["mtime"].asInt();backinfo._real_path = root[i]["real_path"].asString();backinfo._pack_path = root[i]["pack_path"].asString();backinfo._url_path = root[i]["url_path"].asString();// 获取的一个文件备份信息插入到哈希表中Insert(backinfo);}return true;}// 新增数据bool Insert(const Backinfo& backinfo){// 读写锁,在写入时,将写进行加锁,读不加锁,一次只能有一个线程在执行写操作pthread_rwlock_wrlock(&_rwlock);// 所有数据都已经加载到哈希表中了,所以此时只需要操作哈希表即可// 在哈希表中我们以备份文件信息中的url路径和文件备份信息构成键值对_table[backinfo._url_path] = backinfo;pthread_rwlock_unlock(&_rwlock);// 每插入一个数据都要持久化存储Storage();return true;}// 更新数据bool Update(const Backinfo& backinfo){pthread_rwlock_wrlock(&_rwlock);_table[backinfo._url_path] = backinfo; // key值相同,会默认覆盖之前的值pthread_rwlock_unlock(&_rwlock);// 每更新一个数据都要持久化存储Storage();return true;}// 查询数据 通过url获取单个文件的备份信息// 当是下载请求时,我们需要获取单个文件的信息,但是如果是页面展示请求,则需要获取多个文件的信息bool GetOneByUrl(const std::string& url, Backinfo* backinfo){pthread_rwlock_wrlock(&_rwlock);auto it = _table.find(url);if (it == _table.end()){std::cout << "GetOneByUrl is not find" << std::endl;pthread_rwlock_unlock(&_rwlock);return false;}pthread_rwlock_unlock(&_rwlock);*backinfo = it->second;return true;}// 通过文件真是路径来获取文件备份信息 通过真实路径查询在备份信息文件中是否有当前文件,判断文件是否被备份bool GetOneByRealpath(const std::string& realpath, Backinfo* backinfo){pthread_rwlock_wrlock(&_rwlock);auto it = _table.begin();for (; it != _table.end(); ++it){if (it->second._real_path == realpath){*backinfo = it->second;pthread_rwlock_unlock(&_rwlock);return true;}}pthread_rwlock_unlock(&_rwlock);return false;}// 获取所有备份文件的信息  用于展示请求bool GetAll(std::vector<Backinfo>* array){pthread_rwlock_wrlock(&_rwlock);auto it = _table.begin();for (;it != _table.end(); ++it){array->push_back(it->second);}pthread_rwlock_unlock(&_rwlock);return true;}// 持久化存储,也就是将已经操作的(插入或修改)加载到内存中的哈希表中的数据写入到备份信息的文件中bool Storage(){// 创建一个备份文件信息的数据std::vector<Backinfo> array;// 获取所有的备份信息到数组中GetAll(&array);// 先将所有的备份信息,存储到json数组中,然后通过序列化, 最后统一写入文件Json::Value root;for (int i = 0;i < array.size(); i++){Json::Value item; // 构建好每一个备份文件信息item["pack_flag"] = array[i]._pack_flag;item["fsize"] = (Json::Int64)array[i]._fsize;item["atime"] = (Json::Int64)array[i]._atime;item["mtime"] = (Json::Int64)array[i]._mtime;item["real_path"] = array[i]._real_path;item["pack_path"] = array[i]._pack_path;item["url_path"] = array[i]._url_path;root.append(item); // 构建好备份文件信息后,写入json数组中}// 序列化std::string body;JsonUtil::Serialization(root, &body);// 此时root被序列化为一个字符串,放入body中FileUtil fu(_backup_file);if (fu.SetContent(body) == false) // 若文件不存在fu.setContent中的ofstream流在打开文件时,会自动创建{std::cout << "DataManager storage set file content error" << std::endl;return false;}return true;}~DataManager(){// 析构时,销毁读写锁pthread_rwlock_destroy(&_rwlock);}private:std::string _backup_file; // 持久化存储的文件,也就是配置文件中 备份文件信息的文件pthread_rwlock_t _rwlock; // 读写锁, -- 读共享,写互斥std::unordered_map<std::string, Backinfo> _table; // 一个哈希表,用来高效管理数据};

测试数据管理功能

void TestDataManager(const std::string& realpath)
{hhbcloud::Backinfo info;if (info.NewBackinfo(realpath) == false){return;}std::cout << info._pack_flag << std::endl;std::cout << info._atime << std::endl;std::cout << info._mtime << std::endl;std::cout << info._fsize << std::endl;std::cout << info._pack_path << std::endl;std::cout << info._real_path << std::endl;std::cout << info._url_path << std::endl;hhbcloud::DataManager DM;DM.Insert(info);hhbcloud::Backinfo backinfo;DM.GetOneByUrl(info._url_path, &backinfo);std::cout << "================Insert && GetOneByUrl=====================" << std::endl;std::cout << backinfo._pack_flag << std::endl;std::cout << backinfo._atime << std::endl;std::cout << backinfo._mtime << std::endl;std::cout << backinfo._fsize << std::endl;std::cout << backinfo._pack_path << std::endl;std::cout << backinfo._real_path << std::endl;std::cout << backinfo._url_path << std::endl;info._pack_flag = true;DM.Update(info);std::vector<hhbcloud::Backinfo> array;DM.GetAll(&array);std::cout << "===================Update && GetAll=====================" << std::endl;for (auto& e : array){std::cout << e._pack_flag << std::endl;std::cout << e._atime << std::endl;std::cout << e._mtime << std::endl;std::cout << e._fsize << std::endl;std::cout << e._pack_path << std::endl;std::cout << e._real_path << std::endl;std::cout << e._url_path << std::endl;}hhbcloud::Backinfo tmp;DM.GetOneByRealpath(realpath, &tmp);std::cout << "=================GetOneByRealpath=======================" << std::endl;std::cout << tmp._pack_flag << std::endl;std::cout << tmp._atime << std::endl;std::cout << tmp._mtime << std::endl;std::cout << tmp._fsize << std::endl;std::cout << tmp._pack_path << std::endl;std::cout << tmp._real_path << std::endl;std::cout << tmp._url_path << std::endl;}

在这里插入图片描述

九、服务端热点管理的实现

  • 持续检测备份目录下的文件,若满足条件则进行压缩处理。压缩后的文件放入压缩目录下,删除备份目录中的文件
    在这里插入图片描述
extern hhbcloud::DataManager* data; // 创建一个全局的数据管理类,便于对数据的操作namespace hhbcloud
{class HotManager // 热点管理类{public:HotManager(){// 备份文件,压缩文件,最后一次访问时间,压缩文件前缀均可从配置文件中读取Config* config = Config::GetInstance();_back_dir = config->GetBackDir();_pack_dir = config->GetPackDir();_hot_time = config->GetHotTime();_packfile_suffix = config->GetPackfileSuffix();// 如果压缩文件目录和备份文件目录不存在,则创建目录FileUtil fu1(_back_dir);fu1.CreateDirectory(); // 函数内部对文件存在做了判断,若文件已经存在,则直接返回FileUtil fu2(_pack_dir);fu2.CreateDirectory();}// 运行模块bool RunMoudle(){// 热点管理需要不断循环检测备份文件目录下的文件因此是一个死循环while (1){// 首先浏览指定目录下的所有文件FileUtil fu(_back_dir);std::vector<std::string> array;fu.ScanDirectory(&array);for (auto& e : array) // 循环每个文件,判断是否是热点文件,是否需要压缩{if (HotJundge(e) == false){// 热点文件 直接跳过,不用管continue;}Backinfo info;// 此时为非热点文件//从备份信文件中无法找到,文件存在,但是没有备份if (data->GetOneByRealpath(e, &info) == false) {info.NewBackinfo(e);// 给文件创建一个备份信息}// 此时info中的已经有了备份信息(在备份信息文件中找到的, 或者对遗漏的文件创建的备份信息)FileUtil tmp(e);tmp.Compress(info._pack_path); // 对文件进行压缩// 删除原来的备份文件tmp.Remove();info._pack_flag = true;data->Update(info);}usleep(1000); // 为了避免空目录持续循环,造成cpu资源消耗过高,间隔1000ms也就是0.1s}return true;}private:bool HotJundge(const std::string& filename){FileUtil fu(filename);time_t last_atime = fu.LastATime();time_t cur_time = time(nullptr);if ((cur_time - last_atime) > _hot_time){// 最后一次访问时间 - 当前时间 如果大于 热点判断时间,说明为非热点文件,需要压缩return true;}return false;}private:std::string _back_dir; // 备份文件,热点管理类需要从备份文件中获取所有文件判断热点,所以需要备份文件std::string _pack_dir;  // 压缩文件,热点管理类对非热点文件进行压缩处理,并存放在压缩目录下std::string _packfile_suffix; // 压缩文件的后缀int _hot_time; // 热点判断时间};
}

测试热点管理功能

hhbcloud::DataManager* data;void TestHotManager()
{data = new hhbcloud::DataManager;hhbcloud::HotManager ht;ht.RunMoudle();
}

在这里插入图片描述

十、服务端网络通信及业务处理实现

  • 下载httplib第三方库,并将httplib.h复制到当前目录下
  • 使用httplib第三方库搭建服务器,并处理上传,页面展示,和下载业务
    在这里插入图片描述
extern hhbcloud::DataManager* data;
namespace hhbcloud
{class Service{public:Service(){Config* config = Config::GetInstance();// 从配置文件中读取服务器端口,服务器ip,下载前缀_server_port = config->GetServerPort();_server_ip = config->GetServerIp();_download_prefix = config->GetDownloadPrefix();// 若文件此时没有指定备份目录,则需要创建文件目录std::string back_dir = config->GetBackDir();FileUtil fu(back_dir);fu.CreateDirectory(); // createDirectory函数内部对文件存在做了判断,如存在则不创建}bool RunMoudle() // 运行模块{// post请求上传文件, 并且业务处理函数Upload的参数必须是Request 和 Response,所以必须要静态,否则会有隐含的this指针参数_server.Post("/upload", Upload);_server.Get("/listshow", ListShow);_server.Get("/", ListShow);std::string download = _download_prefix + "(.*)"; // .*正则表达式表示匹配任意字符,任意次_server.Get(download, DownLoad);// listen启动服务器,并指定监听的主机和端口_server.listen(_server_ip.c_str(), _server_port);return true;}private:static void Upload(const httplib::Request& req, httplib::Response& resp){auto ret = req.has_file("file"); // 判断客户端上传有没有这个字段if (ret == false){// 没有这个字段,服务器无法处理,设置状态码为400,并返回resp.status = 400;return;}std::cout << "has_file && upload" << std::endl;// 有字段,获取文件内容auto file = req.get_file_value("file");// 在备份文件目录下创建文件,并写入数据std::string back_dir = Config::GetInstance()->GetBackDir();// 获取文件的实际存储路径 -- /backdir/filename std::string realpath = back_dir + FileUtil(file.filename).FileName();FileUtil fu(realpath);// 向文件中写入数据 写入数据函数中ofstream在打开文件时,若文件不存在,会自动创建文件, 若没有指定目录,则需要自己创建fu.SetContent(file.content);// 组织备份信息Backinfo backinfo;backinfo.NewBackinfo(realpath);data->Insert(backinfo); // 将数据写入备份信息文件中return;}static std::string TimetoStr(time_t time){return ctime(&time);}static void ListShow(const httplib::Request& req, httplib::Response& resp){// 获取备份信息文件中的所有数据std::vector<Backinfo> array;data->GetAll(&array);// 使用备份信息组织html页面 std::stringstream ss;ss << "<html><meta charset = 'utf-8'><head><title>cloudBackup</title></head>";ss << "<body><h1>Download</h1><table>";for (auto& e : array){// 构建每一行的数据 文件名称 文件最近访问时间 文件大小(k)ss << "<tr>";std::string filename = FileUtil(e._real_path).FileName();ss << "<td>" << "<a href = '" << e._url_path << "'>" << filename << "</a></td>";ss << "<td>" << TimetoStr(e._atime) << "</td>";ss << "<td>" << e._fsize / 1024 << " K" << "</td>";ss << "</tr>";}ss << "</table></body></html>";resp.body = ss.str();resp.status = 200;resp.set_header("Content-Type", "text/html");return;}// 由文件名,文件大小, 文件最后一次修改构成的ETag信息static std::string GetETag(const Backinfo& backinfo){FileUtil fu(backinfo._real_path);std::string ETag = fu.FileName();ETag += "-";ETag += fu.FileSize();ETag += "-";ETag += fu.LastMTime();return ETag;}static void DownLoad(const httplib::Request& req, httplib::Response& resp){// 通过url路径获取文件的备份信息// req.path就是文件的url路径Backinfo backinfo;data->GetOneByUrl(req.path, &backinfo);// 如果文件压缩标志是true,则需要先解压缩if (backinfo._pack_flag == true){FileUtil fu(backinfo._pack_path);fu.Uncompress(backinfo._real_path); // 解压到真实路径fu.Remove(); // 删除压缩包// 更改文件压缩标志,并更改文件备份信息backinfo._pack_flag = false;data->Update(backinfo);}// 将解压后的文件的内容写到resp.body中FileUtil fu(backinfo._real_path);bool retrans = false; // 标记是否是断电续传std::string old_etag; // 下载请求时,已有的Etag值// 判断是否有if-Range, 若有则是断点续传,若没有则不是if (req.has_header("if-Range")){old_etag = req.get_header_value("if-Range");if (old_etag == GetETag(backinfo))// 有if-Range,但是与当前的Etag不一致,则也需要正常下载{retrans = true;}}if (retrans == false){// 如果retrans == false ,需要断点续传,否则不需要fu.GetContent(&resp.body); // 读取文件内容,写入resp.body// 设置响应报头字段resp.set_header("Accept-Ranges", "bytes"); // 允许断点续传resp.set_header("ETag", GetETag(backinfo));// Content-Type 决定了浏览器如何处理数据,设置文件二进制流,常用于文件下载resp.set_header("Content-Type", "application/octet-stream");resp.status = 200;return;}else{// 此时已经是要断点续传了,按照Range字段中指定的区间,取出指定区间的数据进行下载// httplib库已经内置了,这里只需要取出数据即可fu.GetContent(&resp.body); // 读取文件内容,写入resp.body// 设置响应报头字段resp.set_header("Accept-Ranges", "bytes"); // 允许断点续传resp.set_header("ETag", GetETag(backinfo));// Content-Type 决定了浏览器如何处理数据,设置文件二进制流,常用于文件下载resp.set_header("Content-Type", "application/octet-stream");resp.status = 206;return;}}private:int _server_port; // 服务器开放端口号std::string _server_ip; // 服务器ipstd::string _download_prefix; // 下载前缀httplib::Server _server; // 实例化一个服务器Server对象 ,自动调用构造函数初始化};
}

测试网络通信及业务处理功能
html页面代码:


<html><body><form action = "http://1.14.97.154:8080/upload" method = "post" enctype = "multipart/form-data"><div><input type = "file"  name = "file"></div><div><input type = "submit" value = "上传"> </div></form></body></html>

在这里插入图片描述

void TestServer()
{data = new hhbcloud::DataManager;hhbcloud::Service svr;svr.RunMoudle();
}

在这里插入图片描述

服务端进行多线程同时处理热点管理模块和网络通信及业务处理模块

十一、客户端数据管理

#ifndef __MY_DATAMANAGER__
#define __MY_DATAMANAGER__#include "Util.hpp"
#include <unordered_map>namespace hhbcloud
{class DataManager{public:DataManager(const std::string& backup_file):_backup_file(backup_file){InitLoad();}int Split(const std::string& body, const std::string& sep, std::vector<std::string>* array){int count = 0;int start = 0;while (1){size_t pos = body.find(sep, start);if (pos == std::string::npos){break;}if (start == pos){start = start + sep.size();continue;}array->push_back(body.substr(start, (pos - start) + 1));count++;start = pos + sep.size();}if (start < body.size()){array->push_back(body.substr(start));count++;}return count;}bool InitLoad(){FileUtil fu(_backup_file);if (fu.Exists() == false){std::cout << "client Data manager file is not exists" << std::endl;return false;}std::string body;fu.GetContent(&body);std::vector<std::string> array;Split(body, "\n", &array);for (auto& e : array){std::vector<std::string> tmp;Split(e, " ", &tmp);if (tmp.size() != 2){continue;}_table[tmp[0]] = tmp[1];}return true;}bool Storage(){std::stringstream ss;auto it = _table.begin();for (; it != _table.end(); ++it){ss << it->first << " " << it->second << "\n";}FileUtil fu(_backup_file);fu.SetContent(ss.str());return true;}bool Insert(const std::string& key, const std::string& val){_table[key] = val;Storage();return true;}bool Update(const std::string& key, const std::string& val){_table[key] = val;Storage();return true;}bool GetOneByKey(const std::string& key, std::string* val){auto it = _table.find(key);if (it == _table.end()){return false;}*val = it->second;return true;}private:std::string _backup_file;std::unordered_map<std::string, std::string> _table;};
}#endif

十二、客户端网络通信及备份请求

#define  _CRT_SECURE_NO_WARNINGSf#ifndef __MY_BACKUP__
#define __MY_BACKUP__#include "Util.hpp"
#include "DataManager.hpp"
#include "httplib.h"
#include <Windows.h>#define SERVER_ADDR "1.14.97.154"
#define SERVER_PORT 8080namespace hhbcloud
{class Backup{public:Backup(const std::string& back_dir, const std::string& backup_file):_back_dir(back_dir){FileUtil fu(_back_dir);if (fu.Exists() == false){fu.CreateDirectorys();}_data = new DataManager(backup_file); // 初始化数据管理类对象}// 运行模块 持续的,并进行上传bool RunMoudle(){while (1){// 浏览备份目录下的所有文件,并将数据存放在数组中std::vector<std::string> array;FileUtil fu(_back_dir);fu.ScanDirectory(&array);// 获取的文件,for (auto& e : array){// 判断文件是否需要上传if (IsNeedUpload(e) == false){// 不需要上传,则跳过continue;}// 判断文件是否上传成功if (Upload(e) == true){// 文件若上传成功,在备份信息文件中插入数据// 创建文件唯一标识(文件名 - 文件大小 - 文件最后一次修改时间),std::string id = GetFileIdentify(e); _data->Insert(e, id); //并将数据插入文件备份信息文件中}}Sleep(1); // 间隔1ms检测一次,防止cpu资源消耗过多}}private:// 创建文件唯一标识std::string GetFileIdentify(const std::string& filename){std::stringstream ss;FileUtil fu(filename);ss << fu.FileName() << "-" << fu.FileSize() << "-" << fu.LastMTime();return ss.str();}bool IsNeedUpload(const std::string filename){std::string id;if (_data->GetOneByKey(filename, &id) != false){std::string new_id = GetFileIdentify(filename);if (id == new_id){return false;}}FileUtil fu(filename);if (time(nullptr) - fu.LastMTime() < 3){return false;}return true;}bool Upload(const std::string& filename){FileUtil fu(filename);std::string body;fu.GetContent(&body);httplib::Client client(SERVER_ADDR, SERVER_PORT);httplib::MultipartFormData item;item.content = body;item.filename = fu.FileName();item.name = "file";item.content_type = "application/octet-stream";httplib::MultipartFormDataItems items;items.push_back(item);auto ret = client.Post("/upload", items);if (ret->status != 200 || ret == nullptr){return false;}return true;}private:std::string _back_dir;DataManager* _data;};
}#endif
  • 项目改进
    服务端对文件压缩处理时,可以使用多线程,提高效率。

总结

云备份实战项目主要是基于httplib, bundle, jsoncpp等第三方库实现客户端对文件的备份等功能

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

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

相关文章

关于ConstarintLayout有关的点

目录 一、概述 二、过程。 1、介绍 主要特点 关键概念 使用示例 总结 2、我遇到的问题 问题&#xff1a; 可能的原因&#xff1a; 结论 一、概述 在学习过程中&#xff0c;发现对ConstarintLayout理解不够到位&#xff0c;下面是发现并解决问题过程。 二、过程。 1…

《数字图像处理基础》学习07-图像几何变换之最近邻插值法放大图像

目录 一&#xff0c;概念 二&#xff0c;题目及matlab实现 1&#xff0c;解题思路 2&#xff0c;matlab实现 1&#xff09;matlab思路 2&#xff09;完整代码 三&#xff0c;放大图像及matlab实现 一&#xff0c;概念 通过上一篇&#xff0c;我已经学习了使用最邻近插…

计网-子网划分

基于本视频观看做的笔记&#xff0c;帮助自己理解 子网掩码&#xff1a;用于识别IP地址中的网络号和主机号的位数 表示方法 第一种.32位二进制数字&#xff0c;在子网掩码中&#xff0c;网络号用”1“表示&#xff0c;主机号用”0“表示 e.g.:IP地址1.1.1.1的子网掩码是255…

【Solidity】入门指南:智能合约开发基础

&#x1f308;个人主页: 鑫宝Code &#x1f525;热门专栏: 闲话杂谈&#xff5c; 炫酷HTML | JavaScript基础 ​&#x1f4ab;个人格言: "如无必要&#xff0c;勿增实体" 文章目录 Solidity入门指南&#xff1a;智能合约开发基础引言1. 开发环境搭建1.1 Remix I…

如何高效地架构一个Java项目

引言 Java是企业级应用开发的主流语言之一&#xff0c;而我们作为使用Java语言的程序员&#xff0c;职称有初级、中级、高级、资深、经理、架构&#xff0c;但我们往往只是慢慢通过经验的积累迭代了自己的等级&#xff0c;如果没有保持学习的习惯&#xff0c;大多数程序员会停留…

HTTP 探秘之旅:从入门到未来

文章目录 导言&#xff1a;目录&#xff1a;第一篇&#xff1a;HTTP&#xff0c;互联网的“快递员”第二篇&#xff1a;从点开网页到看到内容&#xff0c;HTTP 究竟做了什么&#xff1f;第三篇&#xff1a;HTTP 的烦恼与进化史第四篇&#xff1a;HTTP 的铠甲——HTTPS 的故事第…

c++:thread(线程)

1.基本使用 1.1创建线程 join()函数用于&#xff1a;主程序等待子线程执行完毕之后再继续 #include <iostream> #include <thread>void printHello() {std::cout << "hello world" << std::endl; }int main() {// 1.创建线程std::thread …

第六届金盾信安杯-SSRF

操作内容&#xff1a; 进入环境 可以查询网站信息 查询环境url https://114.55.67.167:52263/flag.php 返回 flag 就在这 https://114.55.67.167:52263/flag.php 把这个转换成短连接&#xff0c;然后再提交 得出 flag

SpringMVC(2)

前言 这一节我们终结springmvc 1. SSM整合配置 先导入坐标 先创建包 配置类 然后开始创建jdbc的config和mybatis的config&#xff0c;还有properties SpringConfig&#xff1a; jdbc.properties&#xff1a; JdbcConfig: MybatisConfig: 下面开始spring整合mvc&#…

零基础Python学习

1.环境搭建 1.1 安装运行环境python3.13 Welcome to Python.org 1.2 安装集成开发环境PyCharm PyCharm: the Python IDE for data science and web development 1.3 创建项目 && 设置字体 2.基础语法 2.1 常量与表达式 在python中整数除整数不会优化&#xff0c;所…

vue3项目创建方式记录

目录 创建vue3常用的方式有三种&#xff1a;一.使用vue cli创建二.使用vite创建三.使用vue3官方推荐创建方式&#xff08;create-vue&#xff09; 创建vue3常用的方式有三种&#xff1a; 一.使用vue cli创建 vue create 项目名二.使用vite创建 vite是下一代前端开发与构建工…

用MATLAB符号工具建立机器人的动力学模型

目录 介绍代码功能演示拉格朗日方法回顾求解符号表达式数值求解 介绍 开发机器人过程中经常需要用牛顿-拉格朗日法建立机器人的动力学模型&#xff0c;表示为二阶微分方程组。本文以一个二杆系统为例&#xff0c;介绍如何用MATLAB符号工具得到微分方程表达式&#xff0c;只需要…

SpringAi整合大模型(进阶版)

进阶版是在基础的对话版之上进行新增功能。 如果还没弄出基础版的&#xff0c;请参考 https://blog.csdn.net/weixin_54925172/article/details/144143523?sharetypeblogdetail&sharerId144143523&sharereferPC&sharesourceweixin_54925172&spm1011.2480.30…

Android电视项目焦点跨层级流转

1. 背景 在智家电视项目中&#xff0c;主要操作方式不是触摸&#xff0c;而是遥控器&#xff0c;通过Focus进行移动&#xff0c;确定点击进行的交互&#xff0c;所以在电视项目中焦点、选中、确定、返回这几个交互比较重要。由于电视屏比较大&#xff0c;在一些复杂页面中会存…

yolo辅助我们健身锻炼

使用软件辅助健身能够大大提升运动效果并帮助你更轻松地达成健身目标。确保每次锻炼都更加高效且针对性强,精确记录你的训练进度,帮助你更清晰地看到自己的进步,避免无效训练。 借助YOLO11的尖端计算机视觉技术,跟踪和分析锻炼变得异常简单。它可以无缝检测和监控多种锻炼…

Flume 与 Kafka 整合实战

目录 一、Kafka 作为 Source【数据进入到kafka中&#xff0c;抽取出来】 &#xff08;一&#xff09;环境准备与配置文件创建 &#xff08;二&#xff09;创建主题 &#xff08;三&#xff09;测试步骤 二、Kafka 作为 Sink数据从别的地方抽取到kafka里面】 &#xff08;…

SRS搭建直播推流服务

学习链接 5分钟教你搭建SRS流媒体服务器 - B站视频 SRS Stack 入门B站合集视频 - SRS官方教程 SRS官网 SRS官网文档 ossrs/srs github SRS for window - 可以安装windows版本的srs&#xff0c;SRS 5.0.89正式支持Windows&#xff0c;每个5.0的版本都会提供安装包 文章目录…

css—轮播图实现

一、背景 最近和朋友在一起讨论的时候&#xff0c;我们提出了这样的一个提问&#xff0c;难道轮播图的效果只能通过js来实现吗&#xff1f;经过我们的一系列的争论&#xff0c;发现了这是可以通过纯css来实现这一效果的&#xff0c;CSS轮播图也是一种常见的网页展示方式&#x…

nacos安装部署

nacos安装部署 1.安装nacos 1.安装nacos nacos的安装很简单下载后解压启动即可&#xff0c;但是在启动前请确保jdk环境正常&#xff1b; 1.首先我们要下载nacos安装包&#xff1a;可以到官网下载&#xff0c;注意我这里使用的是2.1.0版本&#xff1b; 2.下载完成后&#xff0…

tomcat 8.5.35安装及配置

安装包地址&#xff1a; 1.Index of /dist/tomcat/tomcat-8/v8.5.35/binhttps://archive.apache.org/dist/tomcat/tomcat-8/v8.5.35/bin/ 2.通过网盘分享的文件&#xff1a;tomcat 链接: https://pan.baidu.com/s/1z9bD4rIuIRvzQ4okm3iRzw?pwdp24p 提取码: p24p 3.通过官网…