【云备份项目总结】客户端篇

简介: 【云备份项目总结】客户端篇

客户端的代码与服务端的代码实现有很多相似之处,我们也只编写一个简单的客户端代码。

整体回顾

端要实现的功能是:对指定文件夹中的文件自动进行备份上传。但是并不是所有的文件每次都需要上传,我们需要能够判断,哪些文件需要上传,哪些不需要,因此需要将备份的文件信息给管理起来,作为下一次文件是否需要备份的判断。因此需要被管理的信息包含以下:

1.文件路径名称

2.文件唯一标识:由文件名,最后一次修改时间,文件大小组成的一串信息

所以也需要对文件进行操作的fileutil工具,这个其实与服务端的文件实用工具类颇为相似,直接复制过来即可。

同时也需要对文件进行管理,需要datamanager模块。

在之后就是对文件进行上传的文件备份backup模块

util.hpp

与服务端类似。

namespace fs = std::experimental::filesystem;
class FileUtil {
private:
  std::string _name;
public:
  FileUtil(const std::string &name) :_name(name) {}
  size_t FileSize();
  time_t LastATime();
  time_t LastMTime();
  std::string FileName();
  bool GetPosLen(std::string *content, size_t pos, size_t len);
  bool GetContent(std::string *content);
  bool SetContent(const std::string &content);
  bool Exists();
  bool CreateDirectory();
  bool ScanDirectory(std::vector<std::string> *arry);
};

data.hpp

与服务端的差别是在_table 中的val 存储的值的类型不同,其余的也几乎都相同,都是对文件数据的增查改,对文件数据进行永久化储存,程序运行时的初始化。

class DataManager{
private:
  std::unordered_map<std::string, std::string> _table;
  std::string _back_file;
public:
  DataManager(const std::string back_file);
  bool InitLoad();//程序运行时加载以前的数据
  bool Storage();//持久化存储
  bool Insert(const std::string &key, const std::string &val);
  bool Update(const std::string &key, const std::string &val);
  bool GetOneByKey(const std::string &key, std::string *val);
};

cloud.hpp

搭建客户端,然后循环检测被管理目录下的文件是否需要被备份。

#define SRV_IP "1.1.1.1"
#define SRV_PORT 9000
class BackUp {
 private:
   DataManager *_data;
   std::string _back_dir;
   std::string _back_file;
   bool Upload(const std::string &filename);
   bool IsCanBeUpload(const std::string &filename);
    std::string GetFileIdentifier(const std::string &filename);
 public:
   BackUp(const std::string &back_dir, const std::string &back_file)
                 : _back_dir(back_dir)
   , _back_file(back_file){}
   bool RunModule();
 };

需要注意的部分是,在判断文件是否需要被备份时的条件,具体会在代码部分指出。

整个客户端大致就是如此了。

代码

util.hpp

#pragma once
#define _SILENCE_EXPERIMENTAL_FILESYSTEM_DEPRECATION_WARNING
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <memory>
#include <experimental/filesystem>
//   FileUtile 工具提供了对文件的增删查改的功能,
//              也提供了对目录的查看功能,和创建目录的功能
namespace Cloud
{
  namespace fs = std::experimental::filesystem;
  class FileUtil
  {
  private:
    std::string _Filename;
  public:
    FileUtil(std::string fname) : _Filename(fname)
    {
      // std::cout << fname << std::endl;
    }
    size_t Filesize() // 提取文件大小
    {
      struct stat st;
      if (stat(_Filename.c_str(), &st) < 0)
      {
        std::cout << "get Filesize fail"
              << std::endl;
        return 0;
      }
      return st.st_size;
    }
    std::string Filename() // 提取文件名
    {
      // /a/b/文件名
      size_t pos = _Filename.find_last_of("\\");
      if (pos == std::string::npos)
      {
        return _Filename;
      }
      return _Filename.substr(pos + 1);
    } 
    time_t LastMtime() // 提取文件最后一次的修改时间(文件内容)
    {
      struct stat st;
      if (stat(_Filename.c_str(), &st) < 0)
      {
        std::cout << "get File LastMtime fail"
              << std::endl;
        return -1;
      }
      return st.st_mtime;
    }
    time_t LastAtime() // 提取文件最后一次的访问时间
    {
      struct stat st;
      if (stat(_Filename.c_str(), &st) < 0)
      {
        std::cout << "get File LastAtime fail"
              << std::endl;
        return -1;
      }
      return st.st_atime;
    }
    time_t LastCtime() // 提取文件最后一次的修改时间(文件内容 || 文件属性)
    {
      struct stat st;
      if (stat(_Filename.c_str(), &st) < 0)
      {
        std::cout << "get File LastCtime fail"
              << std::endl;
        return -1;
      }
      return st.st_ctime;
    }
    bool Remove()
    {
      if (this->Exists() == false) {
        return true;
      }
      remove(_Filename.c_str());
      return true;
    }
    bool GetPosLen(std::string &body, size_t pos, size_t len)
    {
      size_t fsize = this->Filesize();
      if (pos + len > fsize)
      {
        std::cout << "get file len is error\n";
        return false;
      }
      std::ifstream ifs;
      ifs.open(_Filename, std::ios::binary);
      if (ifs.is_open() == false)
      {
        std::cout << "read open file failed!\n";
        return false;
      }
      ifs.seekg(pos, std::ios::beg);
      body.resize(len);
      ifs.read(&body[0], len);
      if (ifs.good() == false)
      {
        std::cout << "get file content failed\n";
        ifs.close();
        return false;
      }
      ifs.close();
      return true;
    }
    bool GetContent(std::string &body)
    {
      size_t fsize = this->Filesize();
      return GetPosLen(body, 0, fsize);
    }
    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 failed!\n";
        return false;
      }
      ofs.write(&body[0], body.size());
      if (ofs.good() == false)
      {
        std::cout << "write file content failed!\n";
        ofs.close();
        return false;
      }
      ofs.close();
      return true;
    }
    bool Exists()
    {
      return fs::exists(_Filename);
    }
    bool CreateDirectory()
    {
      if (this->Exists())
        return true;
      return fs::create_directories(_Filename);
    }
    bool ScanDirectory(std::vector<std::string> &arry)
    {
      CreateDirectory();
      for (auto &p : fs::directory_iterator(_Filename))
      {
        if (fs::is_directory(p) == true)
        {
          continue;
        }
        // relative_path 带有路径的文件名
        arry.push_back(fs::path(p).relative_path().string());
      }
      return true;
    }
  };

data.hpp

#pragma once
#include "util.hpp"
#include<unordered_map>
#include<sstream>
namespace Cloud 
{
  #define SEP " "
  class DataManager {
  private:
    std::string _backup_file;   // 存储文件信息的文件
    std::unordered_map<std::string, std::string> _table; 
    int Split(std::string& str, std::string sep, std::vector<std::string>* arry)
    {
      int count = 0;
      size_t pos = 0, idx = 0;
      while (true)
      {
        pos = str.find(sep, idx);
        if (pos == idx)
        {
          idx += sep.size();
          continue;
        }
        if (pos == std::string::npos)
        {
          break;
        }
        arry->push_back(str.substr(idx, pos - idx));
        idx = pos + sep.size();
        count++;
      }
      if (idx < str.size())
      {
        arry->push_back(str.substr(idx));
        count++;
      }
      return count;
    }
  public:
    DataManager(const std::string& backup_file):_backup_file(backup_file)
    {
      InitLoad();
    }
    bool Storage() {  // 将_table里的文件信息写入 back_file
      // 1、获取_table的信息
      std::stringstream ss;
      for (const auto& a : _table)
      {
        //2. 将所有信息进行有格式化的组织
        ss << a.first << SEP << a.second << std::endl;
      }
      //3. 写入到 back_file 文件
      FileUtil fu(_backup_file);
      fu.SetContent(ss.str());
      return true;
    }
    bool InitLoad() {   // 在DataManager 实列化对象时,将_backup_file 里的文件信息提取到 _table中
      // 1. 从 file中提取数据 
      std::string body;
      FileUtil fu(_backup_file);
      fu.GetContent(body);
      // 2. 将数据进行分割,然后放入 _table中。
      std::vector<std::string> arry;
      Split(body, "\n", &arry);
      for (auto& a : arry)
      {
        std::vector<std::string> tmp;
        Split(a, SEP, &tmp);
        if (tmp.size() != 2)
        {
          continue;
        }
        _table[tmp[0]] = tmp[1];
      }
      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;
    }
  };
}

cloud.hpp

#pragma once
#include"httplib.h"
#include"util.hpp"
#include"data.hpp"
namespace Cloud {
#define SERVER_IP "60.204.140.244"
#define SERVER_PORT 9090
  class Backup{
  private:
    std::string _back_dir;   //需要管理的目录
    DataManager* _data;   
    bool IsNeedUpload(const std::string& filename)
    {
      // 1. 如果文件未被备份过,则需要进行备份   2. 如果文件被修改过,则需要重新备份
      FileUtil fu(filename);
      std::string old_id;
      if (_data->GetOneByKey(filename, &old_id))   // 查看文件是否被备份
      {
        //std::cout << old_id << std::endl;
        std::string new_id = GetFileIdentifier(filename);
        if (old_id == new_id)
        {
          return false;
        }
        //一个文件比较大,正在徐徐的拷贝到这个目录下,拷贝需要一个过程,
        //如果每次遍历则都会判断标识不一致需要上传一个几十G的文件会上传上百次
        //因此应该判断一个文件一段时间都没有被修改过了,则才能上传
        // 合理的判断方式应该是判断该文件是否被其他线程占用,是否处于被使用的状态 ,我们采取简单一点的时间判断
        else
        {
          if (time(NULL) - fu.LastMtime() < 10)
            return false;
        }
      }
      //std::cout << old_id << std::endl;
      return true;
    }
    bool Upload(const std::string& file)
    {
      // 1. 获取数据
      FileUtil fu(file);
      std::string body;
      fu.GetContent(body);
      //std::cout << fu.Filename() <<": " << fu.Filesize() << std::endl;
      // 2. 搭建客户端,填充客户端信息
      httplib::Client client(SERVER_IP, SERVER_PORT);
      httplib::MultipartFormData item;
      item.content = body;
      item.filename = fu.Filename();
      item.content_type = "application/octet-stream";   // 表示上传的数据类型为任一数据类型,以二进制形式上传
      item.name = "file";
      httplib::MultipartFormDataItems items;
      items.push_back(item);
      // 3. 上传文件
      auto res = client.Post("/Upload", items);
      if (!res || res->status != 200)
      {
        return false;
      }
      return true;
    }
    std::string GetFileIdentifier(const std::string& filename) {
      // a.txt-fsize-mtime
      FileUtil fu(filename);
      std::stringstream ss;
      ss << fu.Filename() << "-" << fu.Filesize() << "-" << fu.LastMtime();
      return ss.str();
    }
  public:
    Backup(const std::string& back_dir, const std::string back_file):_back_dir(back_dir)
    {
      _data = new DataManager(back_file);
    }
    bool RunModel()
    {
      while (true)
      {
        //1. 遍历文件列表,获取信息
        FileUtil fu(_back_dir);
        std::vector<std::string> arry;
        fu.ScanDirectory(arry);
        //std::cout << arry.size() << std::endl;
        for (auto& a : arry)
        {
          //std::cout << a << std::endl;
          // 获取唯一标识符 ,判断是否需要备份
          if (IsNeedUpload(a)==true)
          {
            std::cout << "文件需要上传\n";
            if (Upload(a) == true)
            {
              std::cout <<GetFileIdentifier(a) << "文件上传成功\n";
              _data->Insert(a, GetFileIdentifier(a));
            }
          }
        }
        Sleep(1000);
      }
      return true;
    }
  };
}


相关文章
|
10月前
|
存储 网络协议 网络安全
文件同步工具 - 本地搭建免费开源的Syncthing自动文件同步服务器【远程访问】
文件同步工具 - 本地搭建免费开源的Syncthing自动文件同步服务器【远程访问】
|
弹性计算 运维 Kubernetes
阿里云上的几种数据备份方式
提供OSS、RDS、ECS、ACK、云原生velero备份方案。
8897 2
阿里云上的几种数据备份方式
|
3月前
|
弹性计算 Linux Windows
服务器迁移
本实践以小微客户为例,提供两个场景的迁移验证实践。采用 SMC 模式,一个以阿里云内部迁移举例;一个以他云服务器为例,迁移到华东2(上海)。
服务器迁移
|
存储 前端开发 Cloud Native
阿里云NAS原生无代理备份通通免费两个月
可前往混合云备份控制台开通前面我们写过不少关于阿里云NAS原生无代理备份的文章,经过若干个版本的迭代,NAS备份(我们此文中提及的NAS备份均指原生无代理备份)不但使用上更加简洁,还新增了不少功能性的变化。
1743 0
阿里云NAS原生无代理备份通通免费两个月
|
存储 Linux 对象存储
云存储网关归档管理的前提条件及步骤
通过设置OSS Bucket的生命周期规则和自动归档操作,实现文件网关中的文件自动归档。
712 0
|
安全 Linux Windows
阿里云混合云备份如何配置备份库和客户端?
阿里云混合云备份提供经济、高效、安全的备份。下面我们来介绍一下基于浏览器的用户界面创建和如何配置混合云备份及所有相关的资源。 如果还没有开通混合云备份服务,需要登录到控制台开通服务。首先,登陆混合云备份云端控制台,选择所在区域。
1458 0
|
存储 对象存储
阿里云文件网关备份
云存储网关(Cloud Storage Gateway:简称CSG)是一款可以部署在用户IDC和阿里云上的网关产品。它以阿里云对象存储(OSS)为后端存储,为云上和云下应用提供业界标准的文件服务(NFS和SMB)和块存储服务(iSCSI)。
1353 0
|
容灾 Linux 数据安全/隐私保护