项目实现:云备份③(配置文件加载模块、数据管理模块的实现)

云备份

前言

书接上回:云备份(实用工具类)实现后,接下来会逐步实现不同模块的功能。会频繁用到工具类,如果不熟悉的老铁还是先看看前面的内容,对接下来要讲解的模块实现会有比较好理解。

配置文件加载模块

配置信息的设计

  1. 热点时间判断:设定一个合适的时间,决定在这个时间过后的文件是非热点文件。对于非热点文件进行压缩存储。
  2. 文件下载 URL 前缀路径:通常这个路径是用来判断用户的下载请求,可能是下载文件请求,也可能是查看备份文件列表的请求。
  3. 压缩文件的后缀名 :在这里默认将压缩后的文件后缀设置为 .lz
  4. 上传文件的存放路径:决定了用户上传文件的存储到服务器的哪里。
  5. 压缩包存放位置:对于非热点文件、压缩包 和 热点文件、普通文件要区分存储。决定了非热点文件、压缩文件的存储位置。
  6. 服务器备份信息存储到文件:记录备份文件信息,保证数据的持久化
  7. 服务器的监听IP、监听端口:当客户端运行到其他主机时,不再需要修改程序

单例文件配置类设计

这里的单例模式采用懒汉模式,对饿汉、懒汉单例模式的实现可以参考小编的这篇文章:特殊类设计

单例文件配置类是用来配置信息的,其中主要实现以下几个功能:

  • GetHotTime:提供热点判断时间
  • GetServerPort:提供服务器监听端口
  • GetServerIP:提供服务器监听IP
  • GetDownloadPrefix:提供下载的 URL 前缀路径
  • GetPackFileSuffix:提供压缩包后缀名
  • GetBackDir:提供备份文件的存放目录
  • GetPackDir:提供压缩包存放的目录
  • GetBackupFile:提供信息存储的文件

下面开始进行代码的编写。

  1. 首先创建 cloud.conf 配置文件,编写配置信息。使用的是Json格式进行编写:
bash 复制代码
{
	"hot_time" : 30,
	"server_port" : 9090,
	"server_ip" : "xxx.xxx.xxx.xxx",
	"download_prefix" : "/download/",
	"packfile_suffix" : ".lz",
	"pack_dir" : "./packdir/",
	"back_dir" : "./backdir/",
	"backup_file" : "./cloud.dat"
}

这里的服务器 IP 设置按照个人需求进行改写,当然端口号也是。

  1. 单例文件配置类的实现,创建 config.hpp 文件进行代码的编写:
cpp 复制代码
#ifndef CLOUD_HPP
#define CLOUD_HPP

#include <mutex>
#include "util.hpp"

namespace cloud
{
#define CONFIG_FILE "./cloud.conf"

    class Config
    {
    public:
        // 获取单例对象指针
        static Config *GetInstance()
        {
            // 双检查加锁
            if (_instance == nullptr)
            {
                // 加锁
                _mutex.lock();
                if (_instance == nullptr)
                {
                    return _instance = new Config();
                }
                // 解锁
                _mutex.unlock();
            }
        }

        //提供热点判断时间
        int GetHotTime() { return _hot_time; }
        //提供服务器监听端口
        int GetServerPort() { return _server_port; }
        //提供服务器监听IP
        std::string GetServerIP() { return _server_ip; }
        //提供下载的URL前缀路径 
        std::string GetDownloadPrefix() { return _download_prefix; }
        //提供压缩包后缀名
        std::string GetPackFileSuffix() { return _packfile_suffix; }
        //提供备份文件的存放目录
        std::string GetBackDir() { return _back_dir; }
        //提供压缩包存放的目录
        std::string GetPackDir() { return _pack_dir; }
        //提供信息存储的文件
        std::string GetBackupFile() { return _backup_file; }

    private:
        // 获取cloud.conf文件信息,进行初始化
        bool ReadConfigFile()
        {
            cloud::FileUtil fu(CONFIG_FILE);
            std::string body;
            // 提取文件信息
            if (fu.GetContent(&body) == false)
            {
                std::cout << "load config file failed!\n";
                return false;
            }
            // 将提取的内容进行反序列化操作
            Json::Value root;
            if (JsonUtil::UnSerialize(body, &root) == false)
            {
                std::cout << "parse config file failed!\n";
                return false;
            }
            // 初始化:
            _hot_time = root["hot_time"].asInt();
            _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();

            return true;
        }
        int _hot_time;                // 热点时间判断
        int _server_port;             // 服务器监听端口
        std::string _server_ip;       // 服务器监听IP
        std::string _download_prefix; // 文件下载的URL前缀路径
        std::string _packfile_suffix; // 压缩文件的后缀名
        std::string _pack_dir;        // 压缩包存放目录
        std::string _back_dir;        // 备份文件的存放目录
        std::string _backup_file;     // 提供信息存储的文件

    private:
        Config()
        {
            ReadConfigFile();
        }
        // 防止单例的拷贝与赋值
        Config(const Config &config) = delete;
        Config &operator=(const Config &config) = delete;
        static Config *_instance;
        static std::mutex _mutex;
    };
    // 静态成员的初始化:类外进行
    Config *Config::_instance = nullptr;
    std::mutex Config::_mutex;
}
#endif

测试代码:

cpp 复制代码
#pragma once
#include "util.hpp"
#include "config.hpp"
void ConfigTest()
{
    // 实例化单例
    cloud::Config *config = cloud::Config::GetInstance();

    std::cout << config->GetHotTime() << std::endl;
    std::cout << config->GetServerPort() << std::endl;
    std::cout << config->GetServerIP() << std::endl;
    std::cout << config->GetDownloadPrefix() << std::endl;
    std::cout << config->GetPackFileSuffix() << std::endl;
    std::cout << config->GetBackDir() << std::endl;
    std::cout << config->GetPackDir() << std::endl;
    std::cout << config->GetBackupFile() << std::endl;
}
int main(int argc, char *argv[])
{
    ConfigTest();
    return 0;
}

实现效果:

可以看到,这里将 cloud.conf 文件内容进行了正常输出,初始化也没有问题。

数据管理模块

好的管理模式,在后期可以帮助我们去更好的处理大量的数据。

为了后期更好的管理数据,并且使用这些数据,首先要对文件的以下这些数据信息进行封装管理:

  1. 文件的实际存储路径:客户端要下载文件时,从这个文件中读取数据进行响应。
  2. 文件压缩包存放路径:对于非热点文件会被压缩处理,压缩包的路径也是需要管理的。当用户需要这个文件时,要先对压缩包进行解压,让后再读取响应的内容。
  3. 文件是否压缩的标志位:用于判断文件是否压缩
  4. 文件大小
  5. 文件最后一次访问时间
  6. 文件最后一次修改时间
  7. 文件访问URL中的资源路径path:/download/a.txt

数据信息类设计

在 src 文件下创建 data.hpp 文件,对 BackupInfo 数据信息类进行代码编写。

  • 实现 BackupInfo 数据信息类

BackupInfo 主要功能是对数据信息进行封装,实现如下:

c++ 复制代码
#include "config.hpp"

namespace cloud
{
    struct BackupInfo
    {
        bool pack_flag;        // 压缩文件的标志位
        size_t fsize;          // 文件大小
        time_t mtime;          // 文件最后一次修改时间
        time_t atime;          // 文件最后一次访问时间
        std::string real_path; // 文件的实际存储路径
        std::string pack_path; // 文件压缩包存放路径
        std::string url;       // URL中的资源路径path

        //初始化信息
        bool NewBackupInfo(const std::string& realpath)
        {
            FileUtil fu(realpath);
            if(fu.Exists() == false)
            {
                std::cout << "new backupbnfo : file is not exists!\n";
                return false;
            }
            Config* config = Config::GetInstance();
            //获取配置文件信息
            std::string packdir = config->GetPackDir();
            std::string packsuffix = config->GetPackFileSuffix(); //获取压缩文件后缀
            std::string download_prefix = config->GetDownloadPrefix(); //文件下载路径

            pack_flag = false; //初始设置为普通文件
            //赋值处理
            fsize = fu.FileSize(); 
            mtime = fu.LastMTime();
            atime = fu.LastATime();
            real_path =  realpath; //文件实际所在的路径
            //./backdir/a.txt -> ./packdir/a.txt.lz
            pack_path = packdir + fu.FileName() + packsuffix; //压缩包路径
            //./backdir/a.txt -> /download/a.txt
            url = download_prefix + fu.FileName(); //文件下载路径
            return true;
        }
    };
}

测试代码:

c++ 复制代码
#include "data.hpp"

void DataTest(const std::string &filename)
{
    cloud::BackupInfo info;
    info.NewBackupInfo(filename);
	//输出BackupInfo数据信息
    std::cout << info.pack_flag << std::endl;
    std::cout << info.fsize << std::endl;
    std::cout << info.mtime << std::endl;
    std::cout << info.atime << std::endl;
    std::cout << info.real_path << std::endl;
    std::cout << info.pack_path << std::endl;
    std::cout << info.url << std::endl;
}
int main(int argc, char *argv[])
{
    DataTest(argv[1]);
    return 0;
}

这里直接拿 src 当前目录下的文件进行测试,实现效果:

数据管理类实现

为了更快的访问数据信息,实现一个数据管理类。

在这里采用 hash 表在内存中管理数据;以 URL 的 path 路径作为key值;拿数据信息类 BackupInfo 对象作为为 val值。

其中,DataManager 数据管理类主要实现以下几个功能:

  1. Insert:支持插入新的数据信息
  2. Update:支持更新数据信息
  3. GetOneByUrl:通过URL资源路径查找对应文件的数据信息
  4. GetOneByRealPath:通过文件路径查找对应的数据信息
  5. GetAll:获取所有的文件数据信息
  • 实现 DataManager 数据管理类

在实现前要讲一点:由于数据可以被多人访问,但是对于写只能允许一个人进行操作。

由此要引入一个 pthread_rwlock_t 读写锁

  • 读写锁 允许多个线程同时读取共享资源,但只允许一个线程进行写操作

下面是 DataManager 类的实现代码:

cpp 复制代码
#include "config.hpp"
#include <unordered_map>
#include <pthread.h>

class DataManager
{
public:
    DataManager()
    {
        // 初始化读写锁
        pthread_rwlock_init(&_rwlock, nullptr);
        // 将拷贝文件的信息进行初始化
        _backup_file = Config::GetInstance()->GetBackupFile();
    }

    ~DataManager()
    {
        // 销毁读写锁
        pthread_rwlock_destroy(&_rwlock);
    }
    // 插入数据信息
    bool Insert(const BackupInfo &info)
    {
        pthread_rwlock_wrlock(&_rwlock); // 上锁
        _table[info.url] = info;         // 数据不存在直接进行插入
        pthread_rwlock_unlock(&_rwlock); // 解锁

        return true;
    }
    // 更新数据信息
    bool UpDate(const BackupInfo &info)
    {
        pthread_rwlock_wrlock(&_rwlock); // 上锁
        _table[info.url] = info;         // 数据不存在直接进行插入,否则更新对应的内容
        pthread_rwlock_unlock(&_rwlock); // 解锁
        return true;
    }
    // 通过URL的文件资源路径,获取对应的备份文件的数据信息
    bool GetOneByUrl(const std::string &url, BackupInfo *info)
    {
        pthread_rwlock_wrlock(&_rwlock); // 上锁

        auto it = _table.find(url);
        if (it == _table.end())
        {
            // 没有找到
            pthread_rwlock_unlock(&_rwlock); // 解锁
            return false;
        }

        // 找到了
        *info = it->second;              // 迭代器的second就是val值
        pthread_rwlock_unlock(&_rwlock); // 解锁

        return true;
    }
    // 通过文件路径查找备份文件的数据信息
    bool GetOneByRealPath(const std::string &realpath, BackupInfo *info)
    {
        pthread_rwlock_wrlock(&_rwlock); // 上锁

        // 遍历整个_table
        auto it = _table.begin();
        while (it != _table.end())
        {
            if (it->second.real_path == realpath)
            {
                // 文件数据信息匹配到了
                *info = it->second;
                pthread_rwlock_unlock(&_rwlock); // 解锁
                return true;
            }
            ++it;
        }

        // 没有找到
        pthread_rwlock_unlock(&_rwlock); // 解锁
        return false;
    }
    //获取整个数据信息
    bool GetAll(std::vector<BackupInfo> *array) //输出型参数
    {
        pthread_rwlock_wrlock(&_rwlock); // 上锁

        //遍历整个_table
        auto it = _table.begin();
        while(it != _table.end())
        {
            //_table表中的val值都插入array中
            array->push_back(it->second);
            ++it;
        }
        pthread_rwlock_unlock(&_rwlock); // 解锁
        return true;
    }

private:
    pthread_rwlock_t _rwlock;                           // 读写锁
    std::string _backup_file;                           // 备份文件
    std::unordered_map<std::string, BackupInfo> _table; // 文件路径,数据信息对象
};

测试代码:

cpp 复制代码
#include "data.hpp"

void PrintBackUpInfo(const cloud::BackupInfo &info)
{
    std::cout << info.pack_flag << std::endl;
    std::cout << info.fsize << std::endl;
    std::cout << info.mtime << std::endl;
    std::cout << info.atime << std::endl;
    std::cout << info.real_path << std::endl;
    std::cout << info.pack_path << std::endl;
    std::cout << info.url << std::endl;
}
void DataTest(const std::string &filename)
{
    cloud::BackupInfo info;
    info.NewBackupInfo(filename); // 初始化
    cloud::DataManager data;
    std::cout << "------------------Insert------------------------\n";
    data.Insert(info); // 将备份文件数据信息进行插入

    cloud::BackupInfo tmp;
    data.GetOneByUrl("/download/bundle.h", &tmp); // 查看下载目录下是否存在bundle.h文件
    // 打印对应的数据信息
    PrintBackUpInfo(tmp);
    std::cout << "------------------UpDate------------------------\n";
    // 修改刚刚插入数据的内容
    info.pack_flag = true;
    data.UpDate(info);
    std::vector<cloud::BackupInfo> array;
    data.GetAll(&array);
    for (auto &e : array)
    {
        PrintBackUpInfo(e);
    }
    std::cout << "------------------GetOneByRealPath------------------------\n";
    data.GetOneByRealPath(filename, &tmp);
    PrintBackUpInfo(tmp);
}
int main(int argc, char *argv[])
{
    DataTest(argv[1]);
    return 0;
}

在这里还是以 src 目录下的 bundle.h 文件为测试案例,测试结果如下:

数据持久化存储

为了持久化存储管理,采用Json序列化将所有的数据保存在文件中。

完善 DataManager 类,实现 Storage 成员函数。实现步骤如下:

  1. 获取 _table 中所有数据
  2. 将数据添加到 Json::Value 对象中
  3. 对 Json::Value 对象进行序列化操作
  4. 将序列化数据填写到文件

下面只展示当前功能代码,如下:

cpp 复制代码
class DataManager
{
public:
	//...其他成员函数
	
    // 数据持久化操作
    bool Storage()
    {
        // 1.获取所有的数据信息
        std::vector<BackupInfo> array;
        GetAll(&array);
        // 2.将获取的数据添加到Json::Value对象
        Json::Value root;
        for (int i = 0; i < array.size(); i++)
        {
            Json::Value tmp;
            tmp["pack_flag"] = array[i].pack_flag;
            // Json不认识 time_t、size_t类型,需要强制类型转换
            tmp["fsize"] = (Json::Int64)array[i].fsize;
            tmp["mtime"] = (Json::Int64)array[i].mtime;
            tmp["atime"] = (Json::Int64)array[i].atime;
            tmp["real_path"] = array[i].real_path;
            tmp["pack_path"] = array[i].pack_path;
            tmp["url"] = array[i].url;
            // 整个数据不仅仅只有一份
            root[i].append(tmp);
        }
        // 3.序列化操作
        std::string body;
        if(JsonUtil::Serialize(root, &body) == false)
        {
            std::cout << "Data write failed!\n";
            return false;
        }
        // 4.将数据存储到文件
        FileUtil fu(_backup_file); // 写入到备份文件
        fu.SetContent(body);
        return true;
    }
private:
    pthread_rwlock_t _rwlock;                           // 读写锁
    std::string _backup_file;                           // 备份文件
    std::unordered_map<std::string, BackupInfo> _table; // 文件路径,数据信息对象
};

数据持久化操作,常用于新的数据插入后、数据更新后。

对此,需要对先前的 插入数据、更新数据的函数增添数据持久化功能,下面是 Insert、UpDate 函数修改后的代码:

cpp 复制代码
// 插入数据信息
bool Insert(const BackupInfo &info)
{
    pthread_rwlock_wrlock(&_rwlock); // 上锁
    _table[info.url] = info;         // 数据不存在直接进行插入
    pthread_rwlock_unlock(&_rwlock); // 解锁

    //数据持久化
    Storage();
    return true;
}
// 更新数据信息
bool UpDate(const BackupInfo &info)
{
    pthread_rwlock_wrlock(&_rwlock); // 上锁
    _table[info.url] = info;         // 数据不存在直接进行插入,否则更新对应的内容
    pthread_rwlock_unlock(&_rwlock); // 解锁

    //数据持久化
    Storage();
    return true;
}

初始化加载实现

初始化程序运行时,从文件读取数据。

完善 DataManager 类,实现 InitLoad 成员函数。实现步骤如下:

  1. 将数据从文件中读取出来
  2. 反序列化操作
  3. 反序列化得到的数据添加到 _table 中

下面只展示当前功能代码,如下:

cpp 复制代码
class DataManager
{
public:
	//...其他成员函数
	
	//数据初始化加载
    bool InitLoad()
     {
         //1.将数据从文件中读取出来
         FileUtil fu(_backup_file);
         if(fu.Exists() == false)
         {
             //文件不存在
             std::cout << "The back file does not exist\n";
             return false;
         }
         std::string body;
         fu.GetContent(&body);

         //2.反序列化
         Json::Value root;
         if(JsonUtil::UnSerialize(body, &root) == false)
         {
             std::cout << "Deserialization failed\n";
             return false;
         }

         //3.将反序列化的数据添加到_table
         for(int i = 0; i < root.size(); i++)
         {
             BackupInfo info;
             info.pack_flag = root[i]["pack_flag"].asBool();
             info.fsize = root[i]["fsize"].asInt64();
             info.mtime = root[i]["mtime"].asInt64();
             info.atime = root[i]["atime"].asInt64();
             info.pack_path = root[i]["pack_path"].asString();
             info.real_path = root[i]["real_path"].asString();
             info.url = root[i]["url"].asString();

             //将info添加到_table中
             Insert(info);
         }
         return true;
     }
private:
    pthread_rwlock_t _rwlock;                           // 读写锁
    std::string _backup_file;                           // 备份文件
    std::unordered_map<std::string, BackupInfo> _table; // 文件路径,数据信息对象
};

初始化一般都会在构造函数内部进行。实现初始化信息加载后,需要对构造函数进行功能添加:

cpp 复制代码
 DataManager()
 {
     // 初始化读写锁
     pthread_rwlock_init(&_rwlock, nullptr);
     // 将拷贝文件的信息进行初始化
     _backup_file = Config::GetInstance()->GetBackupFile();
     //初始化加载
     InitLoad();
 }

到这里,数据管理模块的功能基本实现。

该篇文章主要实现了云备份项目的两个功能模块,由于篇幅过长,剩下模块的内容会在后续博客中体现。喜欢的老铁可以点赞 + 收藏!你们的关注是我持续更新的动力,感谢大家的观看。

相关推荐
2301_1472583691 小时前
7月2日作业
java·linux·服务器
2301_803554523 小时前
c++中类的前置声明
java·开发语言·c++
xuanzdhc5 小时前
Linux 基础IO
linux·运维·服务器
愚润求学5 小时前
【Linux】网络基础
linux·运维·网络
bantinghy6 小时前
Linux进程单例模式运行
linux·服务器·单例模式
小和尚同志7 小时前
29.4k!使用 1Panel 来管理你的服务器吧
linux·运维
帽儿山的枪手7 小时前
为什么Linux需要3种NAT地址转换?一探究竟
linux·网络协议·安全
shadon1789 天前
回答 如何通过inode client的SSLVPN登录之后,访问需要通过域名才能打开的服务
linux
小米里的大麦9 天前
014 Linux 2.6内核进程调度队列(了解)
linux·运维·驱动开发
LyaJpunov9 天前
深入理解 C++ volatile 与 atomic:五大用法解析 + 六大高频考点
c++·面试·volatile·atomic