【C++项目实战】云存储(实现小记)

云存储

服务端功能细分:

1、支持客户端文件上传功能

2、支持客户端文件备份列表查看功能

3、支持客户端文件下载功能(断点续传)

4、热点文件管理功能(对长时间无访问的文件进行压缩存储)

服务端模块划分

1、数据管理模块(管理的备份的文件信息, 以便随时获取)

2、网络通信模块(实现与客户端的网络通信)

3、业务处理模块(上传,列表,下载(断点续传))

4、热点管理模块(对长时间无访问的文件进行压缩存储)

客户端功能细分

1、指定文件夹中的文件检测(获取文件夹中有什么文件)

2、判断指定的文件是否需要备份(新增,已经备份过但是修改了的文件)(上次上传后又修改过,但是已经间隔三秒钟没有被修改)

3、将需要备份的文件上传备份到服务器上

客户端模块划分

1、数据管理模块(备份的文件信息)

2、文件检测模块(监控指定的文件夹)

3、文件备份模块(上传需要备份的文件数据)

升级GCC编译器

sudo yum install centos-release-scl-rh centos-release-scl
sudo yum install devtoolset-7-gcc  devtoolset-7-gcc-c++
source /opt/rh/devtoolset-7/enable
echo "source /opt/rh/devtoolset-7/enable" >> ~/.bashrc

安装Jsoncpp库

sudo yum install epel-release
sudo yum install jsoncpp-devel
ls /usr/include/jsoncpp/json/
assertions.h config.h forwards.h reader.h version.h
autolink.h features.h json.h value.h writer.h #注意,centos版本不同有可能安装的jsoncpp版本不同,安装的头文件位置也就可能不同了。

安装bundle数据压缩库

git clone https://github.com/r-lyeh-archived/bundle.git

安装httplib数据库

git clone https://github.com/yhirose/cpp-httplib.git

其他工具

lrzsz # 本地云服务器文件传输
zip   # 压缩文件
unzip # 解压文件

Json

json 是一种数据交换格式超,采用完全独立于编程语言的文本格式来存储和表示数据

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;							//转float
    bool 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;
}

C++11 原始字符串

R"(字符串)"

bundle

Buindle是一个嵌入式压缩库,支持23种压缩算法和2种存档格式。使用的时候需要加入两个文件bundle.hbundle.cpp即可

md5

md5sum filename

httplib库

httplib库,一个C++11但文件头都跨平台HTTP/HTTPS库,安装起来非常容易,只需要包含httplib.h在代码中即可

httplib哭实际上是用于搭建一个简单的http服务器或者客户端的库,这种第三方网络库,可以让我们免去搭建服务器或客户端的时间,把更多的经历投入到具体的业务处理中,提高开发效率

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 server
        std::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;						// 获取文件信息
    };
  
    struct Response {
        std::string version;				// 协议版本
        int status = -1;						// 响应状态码
        std::string reason;					
        Headers headers;						// 头部字段
        std::string body;						// 正文
        std::string location; 			// Redirect location 重定向位置
        void set_header(const char *key, const char *val);  								// 设置头部字段
        void set_content(const std::string &s, const char *content_type);		// 设置正文
    };
  
    class Server {
        // Handler 函数指针类型,定义一个HTTP请求处理回调函数格式。httplib搭建的服务器收到请求后,进行解析,得到一个Request结构体,其中					包含了请求数据根据请求数据我们可以通过调用Handler方法来处理请求
        // Request 参数,保存请求数据,让用户能够根据请求数据进行业务处理
      	// Response参数,需要用户在业务处理中,填充数据,最终要响应给客户端
        using Handler = std::function<void(const Request &, Response &)>; 
      	// Handlers 是一个请求路由数组:其中包含两个信息
      	// regex : 正则表达式-用于匹配http请求资源路径
        // Handler : 请求处理函数指针
       	// 可以理解, Handlers是一张表,映射了一个客户端请求的资源路径和一个处理函数(用户自己定义的函数),当服务器收到请求解析得到								 Request就会根据资源路径以及请求方法到这张表中查看有没有对应的处理函数,如果有则调用函数进行处理,如果没有响应404
        using Handlers = std::vector<std::pair<std::regex, Handler>>;
      	// 线程池中线程的工作:
      	// 1、接受请求,解析请求,得到Request结构体也就是请求数据
      	// 2、在Handlers映射表中,根据请求信息查找处理函数,如果有则调用函数处理
        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);
      	// 搭建并启动Http服务器
        bool listen(const char *host, int port, int socket_flags = 0);
    };
    class Client {
        Client(const std::string &host, int port);							// 传入服务器IP地址和端口
        Result Get(const char *path, const Headers &headers);		// 向服务器发送GET请求
        Result Post(const char *path, const char *body, size_t content_length,
            const char *content_type);
        Result Post(const char *path, const MultipartFormDataItems &items); // POST请求提交多区域数据,常用语多文件上传
    };
}

文件实用工具类设计

class FileUtil {
	private:
		std::string _filename;
	public:
		size_t FileSize();										// 文件大小
		time_t LastMTime(); 									// 最后修改时间
		time_t LastATime();										// 最后访问时间(判断热点数据)
		std::string FileName();						  	// 获取文件路径名中的文件名称
		bool SetContent(std::string &body); 	// 向文件写入数据
		bool GetContent(std::string *body); 	// 从文件获取数据
		bool GetPosLen(std::string *body, size_t pos, size_t len);  // 获取文件指定位置 指定长度的数据
		bool Exists(); 												// 判断文件是否存在
		bool CreateDirectory(); 							// 创建目录
		bool ScanDirectory(std::vector<std::string> *arry); 					// 获取指定目录下的全部文件名称
  	bool Compress(const std::string &packname);									// 压缩文件
  	bool UnCompress(const std::string &filename);								// 解压文件
		
}

Json实用工具类设计

class JsonUtil{
    public:
        static bool Serialize(const Json::Value &root, std::string *str);
        static bool UnSerialize(const std::string &str, Json::Value *root);

};

服务端配置信息管理模块

1、热点判断时间
	热点管理:多长时间没有被访问的文件算是热点文件
2、文件下载URL前缀路径 -- 用于表示客户端请求是个下载请求
	当用户发来一个备份列表查看请求/listshow,我们如何判断这个不是一个listshow的文件下载请求
	/download/test.txt
3、压缩包后缀名:订立压缩包的命名规则,就是在文件原名称之后加上后缀。 ".lz"
4、上传文件存放路径:决定了文件上传后实际存储在服务器的哪里
5、压缩包存放路径:决定飞热点文件压缩后存放的路径
6、服务端备份信息存放文件:服务端记录的备份文件信息的持久化存储
7、服务器的监听IP地址:当程序要运行在其他主机上,就不需要修改程序
8、服务器的监听端口
#ifndef __CLX_CONFIG_HPP__
#define __CLX_CONFIG_HPP__

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

namespace clx_cloud{
    class Config{

        private:
            Config(){};
            bool ReadConfigFile();hl
            static Config *_instance;
            static std::mutex _mutex;
        private: 
            int _hot_time;
            int _server_port;
            std::string _server_ip;
            std::string _download_prefix;
            std::string _packfile_suffix;
            std::string _pack_dir;
            std::string _back_dir;
            std::string _backup_file;
        public:
            static Config* GetInstance();
            int GetHotTime();
            int GetServerPort();
            std::string GetServerIp();
            std::string GetDownloadPrefix();
            std::string GetPackFileSuffix();
            std::string GetPackDir();
            std::string GetBackDir();
            std::string GetBackUpFile();
    };

    Config* Config::_instance = NULL;
    std::mutex Config::_mutex;
}

#endif
{    
    "hot_time" : 30,                                                                
    "server_port" : 8888,    
    "server_ip" : "101.43.252.201",    
    "download_prefix" : "/download/",    
    "packfile_suffix" : ".lz",    
    "pack_dir" : "./packdir/",    
    "back_dir" : "./backdir/",    
    "backup_file" : "./cloud.dat"    
    
}    

数据管理模块

管理哪些数据,后期需要使用到哪些数据

1、文件实际存储路径:当客户端需要下载文件时,则从这个文件中读取数据进行响应

2、文件压缩包存放路径名:如果这个文件是一个非热点文件会被压缩,则这个就是压缩包的路径名称

​ 如果客户端要下载文件,则需要先进行解压缩,然后读取解压后的文件数据

3、文件是否压缩的标志位:判断文件是否已经被压缩了

4、文件大小

5、文件最后一次修改时间

6、文件最后一次访问时间

7、文件访问URL中资源路径Path (/download/a.txt)

如何管理数据:

1、用于数据信息访问:使用Hash表在内存中管理数据,以URL的path作为key值

2、持久化存储管理 :使用json序列化将所有数据信息保存在文件中

数据管理类:管理服务端系统中会用到的数据

1、数据信息结构体

typedef struct BackupInfo{
  	void NewBackupInfo(const std::string realpath);
		bool pack_flag; // 是否压缩标志
		size_t fsize; 
		time_t atime;
		time_t mtime;
		std::string real_path; // 文件实际存储路径
		std::string pack_path; // 压缩包存储路径名称
		std::string url_path;  
} BackupInfo;

2、数据管理者

class DataManager {
		private:
			std::string _backup_file; // 持久化存储文件
			std::unordered_map<std::string, BackupInfo> _table; // 内存中以hash表存储
			pthread_rwlock_t_rwlock; // 读写锁
		public:
			bool Storage();  // 每次数据新增或者修改都需要重新持久化存储,避免数据丢失
			bool InitLoad(); // 初始化加载,在每次系统重启都要加载以前的数据 
			bool Insert(const BackupInfo &info); // 新增
      bool Update(const BackupInfo &info); // 修改
      bool GetOneByUrl(const std::string &url, BackupInfo *info);
  		bool GetOneByRealpath(const std::string &path, BackupInfo *info);
  		bool GetAll(std::vector<BackupInfo> *array);
};

生成静态库

g++ -c bundle.cpp -o bundle.o   
ar -cr libbundle.a bundle.o     # 生成静态库
# 将静态库拷贝到lib目录下 并修改Makefile
g++ -o $@ $^ -std=c++11 -lpthread -lstdc++fs -ljsoncpp -L./lib -lbundle -g

热点管理模块

对服务器上备份文件进行检测,哪些文件长时间没有被访问,则认为是非热点文件,则进行压缩存储,节省磁盘空间

实现思路:遍历所有文件,检测文件最后一次访问时间,若于当前时间相减得到差值,这个差值如果大于设定好的非热点判断时间则认为是非热点文件,则压缩进行存储,并且删除非热点文件

遍历所有的文件:

​ 1、从数据管理模块中遍历所有的备份文件信息

​ 2、遍历备份文件夹,获取所有的文件进行属性获取,最终判断

选择第二种:遍历文件夹。每次获取文件的最新数据进行判断,并且还可以解决数据信息缺漏的问题

1、遍历备份目录,获取所有文件路径名称

2、逐个文件获取最后一次访问时间与当前系统时间进行比较判断

3、对非热点文件进行压缩处理,删除源文件

4、修改数据管理模块对应的文件信息(压缩标志)

热点管理流程:

获取备份目录下的所有文件

逐个判断文件是否为热点文件

非热点文件进行压缩处理

删除源文件,修改备份信息

服务业务处理模块

将网络通信模块和业务处理模块间进行了合并(网络通信通过httplib库来完成)

1、搭建网络通信服务器:借助httplib完成

2、业务处理请求:客户端浏览器请求一个备份文件的展示页面,响应页面

3、文件下载请求:通过展示页面,点击下载,响应客户端要下载的文件数据

网络通信接口设计:约定好,客户端发送什么样的请求,我们呢给予什么样的响应

请求 : 文件上传 POST /upload

当服务器收到了一个POST方法的/upload请求,我们认为这是一个文件上传的请求,解析请求,得到文件数据,将数据写入到文件中

展示页面. GET /listshow

文件下载 : GET /download/test.txt

服务端业务处理类的设计

http的ETag头部字段:其中存储了一个资源的唯一标识

客户端第一次下载文件的时候,会收到这个响应信息

第二次下载,就会将这个信息发送给服务器,想要让服务器根据这个唯一标识判断这个资源有没有被修改过,如果没有被修改过,直接使用原先缓存的数据,不用重新下载了

http协议本身对于etag中是什么数据并不关心,只要你服务端能够自己标识就行

因此我们的etag就用"文件名-文件大小-最后一次修改时间"组成

而etag字段不仅仅是缓存用到,还有就是后面的断点续传的实现也可以用到,因为断点续传也要保证文件没有被修改过

http协议的Accept-Ranges:bytes字段:用于告诉客户端我支持断点续传,并且数据单位以字结尾单位

Content-Type 字段的重要性 : 决定浏览器如何处理响应正文

断点续传

功能:当文件下载过程中,因为某种异常而中断,如果再次进行从头下载,效率较低,以为需要将之前已经传输过的数据再次传输一遍。因此断点续传就是从上次下载断开的位置,重新下载即可,之前已经传输过的数据不需要重新传输

目的: 提高文件的重新传输效率

实现思想:

客户端在下载文件的时候,要每次接受到数据写入文件后记录自己当前的 下载数据量。

当异常下载中断时,下次断点续传的时候,将要重新下载的数据区间(下载起始位置,结束位置)发送给服务器,服务器收到后,仅仅回传客户端需要的数据即可

需要考虑的问题:如果上次下载文件之后,这个文件在服务器上已经被修改了,这时候不能重新断点续传,而是应该重新下载修改后的文件

HTTP协议中断点续传的实现

​ 主要关键点:

​ 1、告诉服务器下载区间范围

​ 2、服务器上能够检测上一次下载这个文件是否被修改过

Accept-Ranges: bytes 告诉客户端服务器支持断点续传功能

ETag : “” 文件唯一标识,客户端收到响应会保存该信息

断点续传会请求中会抱憾ETag字段,用于服务端判断这个文件是否被修改

GET /download/test.txt HTTP/1.1
If-Range: 服务端在下载时响应etag字段,用于服务端判断这个文件是否与原先下载的文件一致
Range: bytes 100 - 100000 这个字段用于告诉服务器客户端需要的数据区间范围

HTTP/1.1 206 Partial Content
ETag: "asdhasdh"
Content-Range:bytes 100-10000 /文件大小

客户端

​ 要实现的功能:自动对指定文件夹中的文件进行备份

进行模块划分

​ 数据管理模块:管理备份文件信息

​ 目录遍历模块,获取指定文件夹中所有文件路径名

​ 文件备份模块: 将需要备份的文件上传备份到服务器

客户端要备份文件,什么文件需要备份,都是通过数据管理判断的

数据管理模块:要管理的数据设计

​ 数据管理模块:其中的信息用于判断一个文件是否需要重新备份

​ 1、文件是否是新增的

​ 2、不是新增的,则上次备份后 有没有被修改过

​ 管理的数据:文件的路径名,文件的唯一标识

当前客户端的程序开发,是在windows下的,使用的工具是VS至少需要使用2017以上版本(需要支持c++17)

数据管理模块实现思想:

​ 1、内存存储:高访问效率–使用hash表–unordered_map

​ 2、持久化存储: 文件存储

​ 文件存储涉及到数据序列化:因为vs中安装jsoncpp较为麻烦,直接使用自定义序列化格式

​ key val : key 是文件路径名, val是文件唯一标识

  • 25
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小白在进击

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值