云备份

一、概述

项目名称:云备份
开发环境:VS2017、Linux、MYSQL
使用技术:多线程、数据库、http协议
项目功能:将客户端下的文件自动上传至服务端,并可通过浏览器下载备份过的文件
项目简介
服务端:通过cpp-httplib库搭建http服务器,并设置响应请求方法的回调函数,将收到的请求进行处理;通过启动另一个线程来监听所有接受到的文件,如果他们长时间没被访问,则压缩以节省磁盘空间。
客户端:通过cpp-httplib库搭建http客户端,循环的扫描需要被备份的文件夹,在这个监听文件夹里,如果文件是新加入的,则需要备份,如果文件的内容有改变,则也需要备份。客户可通过浏览器去下载备份过的文件。

二、实现原理

服务端
通过从客户端接受来的请求,找到对应的回调函数,如列表请求(在浏览器展示已经备份了的文件)、上传请求(将上传的文件存储到服务端的磁盘里)、下载请求(通过浏览器下载备份过的文件)等。
每次客户端上传了一个文件,则将文件信息(文件名,文件是否被压缩,文件上传时候的时间戳,这三个信息)存储到数据库,另一个线程就是通过扫描数据库,通过 文件上传时候的时间戳 这个属性来判断这个文件是否要被压缩(用现在的时间戳-之前记录的时间戳,如果这个差值大于自己确定的基准,则认为是非热点文件,表示这个文件长时间未访问),来进行压缩,并更新文件信息(是否被压缩的属性,压缩了就置1,没压缩就置0,所以这个属性是tinyint)。
用户通过浏览器访问服务器地址,可以得到上传过的文件列表,其中列表请求里有请求重定向的,点击需要下载的文件,则会向服务器发送一个下载的请求,服务器会解析请求路径里的文件名,然后将文件数据发送给浏览器给客户。
客户端
通过一个文件和一个文件夹来实现文件的管理,存放文件名和文件的md5值的监听文件(用来表示文件上传过,而且存储的是最后一次上传时候的文件属性)、存放需要备份的文件们的监听文件夹(将要备份的文件放到这个文件夹里)。
通过扫描监听文件夹,计算每个文件的md5值,然后去监听文件里找到对应文件记录的md5值(这里的md5值是最后一次上传时候的md5值),对比后有三种情况:1、如果相同,则表示该文件没有被修改过,则不需要上传,2、监听文件里对应文件的md5是空的,表示没有找到对应的文件md5值,表示之前没有上传过该文件,则需要上传,3、监听文件里对应文件的md5不是空的,但是和现在计算的md5值不一样,则表示这个文件修改过,需要上传。

三、模块划分

该项目分为客户端和服务端两大模块,而服务端分为四个模块,客户端分为三个模块

服务端

文件工具、文件信息管理、非热点文件压缩、http服务端。

1. 文件工具
文件工具类又分为两个工具:文件读写工具、压缩和解压缩文件工具。
文件读写工具:通过boost库和文件流,封装了一个写文件和一个读文件的接口,用来将数据写入磁盘文件和将磁盘文件数据读出。
压缩和解压缩文件工具:通过zlib库,设置了两个接口,压缩文件和解压缩文件,用来将一个文件压缩并命名和将一个压缩包解压缩并命名。

2. 文件信息管理
文件信息管理分为两个模块:数据库操作、文件信息管理
数据库操作模块:封装了数据库的初始化语句、执行语句、销毁语句。初始化包括数据库初始化、数据库连接服务器、设置客户端字符编码集;执行语句是将外界传来的mysql执行语句执行;销毁语句是销毁mysql句柄。
文件信息管理:用了mysql、单例模式、读写锁,封装了mysql的执行接口,进行判断文件是否存在、判断文件是否被压缩、判断、获取所有文件列表、获取未压缩的文件列表、获取一个文件的时间戳信息、插入一个文件信息、更新一个文件的信息。然后通过单例模式和读写锁来对外使用这些接口,因为这个文件信息管理模块会被 非热点文件压缩和http服务器 这两个模块用到,单例模式是因为这两个模块都操作同一个数据库,读写锁是因为这两个模块是两个可并发可并行的执行流。

3.非热点文件压缩
这个模块分另一个线程去执行,定时的循环的去扫描客户端上传来的所有文件,通过判断数据库里记录的文件信息(时间戳,用现在的时间戳-之前记录的时间戳,如果差值大于一个基准值则表示长时间未被访问)来判断是否需要将这个文件压缩,一次来节省服务器的磁盘空间。

4.http服务器
通过cpp-httplib库设置请求路径的路由表(请求方法-请求资源路径-回调函数),封装了3个回调函数:文件上传(PUT)、文件列表请求(GET)、文件下载(GET)。
1、文件上传:通过客户端的request请求头部得到要下载的文件名,通过请求正文得到文件的数据;然后判断当前磁盘是否已经有这个文件,如果有则删除原来的文件,然后接受这个新来的文件,否则直接接受。
2、文件列表请求:将所有已经上传了的文件名以text/html文件流的超文本形式作为response应答,其中还会给每个文件名附加上< a href=’/download/filename’ > filename < /a >来让客户通过点击文件名,同时也是超链接,跳转到下载对应文件名的页面。
3、文件下载:通过客户的request请求路径得到需要下载的文件,并判断是存在,如果不存在则将response的状态码设置为404,否则继续判断这个文件在服务器磁盘里是否已经被压缩,如果是已压缩,则另外需要解压然后将数据库的这个文件信息(是否被压缩)更新,然后再将文件数据以application/octet-stream的格式传给客户。

客户端

文件工具、文件信息管理、http客户端

1.文件工具
和服务端的读写工具一样,但是附加了一个计算文件的md5值,md5值是通过计算文件数据得到的字符串,用来唯一标识一个文件。

2.文件信息管理
这里用到了STL的unordered_map容器和boost/filesystem库。封装了三个接口,加载文件信息、存储文件信息、插入/更新文件信息。
1、加载文件信息:将已经备份了的文件的文件信息(文件名和文件md5值)存在一个专门保存文件信息的文件里,每次客户端打开,都会读取这个文件的内容,通过boost库的分割函数将各个文件信息分开,然后将文件名和文件md5以unordered_map的形式读取到内存。
2、存储文件信息:将内存里的unordered_map里的文件信息写入到专门保存文件信息的文件里(从头开始写入,不是追加写入)。
3、插入/更新文件信息:每上传一个文件,就将文件信息插入到unordered_map里,然后调用 存储文件信息,将文件信息存到磁盘里。

3.http客户端
通过cpp-httplib库建立客户端,并连接服务端,然后定时的循环的扫描监听文件夹下的所有文件,如果扫描到文件夹,则会递归的再进入这个函数去扫描这个文件夹,最后把所有的需要备份的文件找出来(通过 专门保存文件信息的文件 找到对应文件的md5,然后和现在这个文件计算出来 的md5对比,如果不同则表示这个文件更改过,则需要更新,如果在 专门保存文件信息的文件 里没有找到对应文件信息,则表示这是新来的文件),然后将文件以application/octet-stream的格式上传给服务端,并且在 unordered_map和专门保存文件信息的文件 更新文件的信息。

四、项目源码

服务端

FIleTool.hpp 文件工具
#pragma once
#include<fstream>
#include<zlib.h>
#include<boost/filesystem.hpp>
#include<string>

namespace FileTool
{
    class FileReadWriteTool
    {
        public:
            //鎶奻ile_name璇诲埌body閲?
            static bool FileRead(const std::string& file_name, std::string* body)
            {
                //1銆佸紑鏂囦欢
                std::ifstream ifs(file_name,std::ios::binary);
                if(ifs.is_open() == false)
                {
                    printf("Readfile[%s] open failed!\n",file_name.c_str());
                    return false;
                }
                //2銆佽绠楁枃浠跺ぇ灏?
                int64_t fsize = boost::filesystem::file_size(file_name);
                //3銆佸皢body鐨勫ぇ灏忔墿瀹规垚file_size澶у皬
                body->resize(fsize);
                //4銆佽鏂囦欢
                ifs.read(&((*body)[0]),fsize);
                //鍒ゆ柇涓婁竴娆℃搷浣滄槸鍚︽垚鍔?
                if(ifs.good() == false)
                {
                    printf("Readfile[%s] read failed!\n",file_name.c_str());
                    ifs.close();
                    return false;
                }
                ifs.close();
                return true;
            }
            //灏哹ody閲岀殑鍐呭鍐欏埌file_name閲?
            static bool FileWrite(const std::string& file_name, const std::string& body)
            {
                //1銆佸紑鏂囦欢
                std::ofstream ofs(file_name,std::ios::binary);
                if(ofs.is_open() == false)
                {
                    printf("Writefile[%s] open failed!\n",file_name.c_str());
                    return false;
                }
                //2銆佸啓鏂囦欢
                ofs.write(&(body[0]),body.size());
                if(ofs.good() == false)
                {
                    printf("Writefile[%s] write failed!\n",file_name.c_str());
                    ofs.close();
                    return false;
                }
                ofs.close();
                return true;
            }
    };
    class FileCompressTool
    {
        public:
            //灏唖rc鍘嬬缉鎴恎z_dst
            static bool Compress(const std::string& src, const std::string& gz_dst)
            {
                //1銆佽鍙杝rc鏂囦欢鐨勫唴瀹?
                std::string buf;
                FileReadWriteTool::FileRead(src,&buf);
                //2銆佷互gzopen鐨勫啓鏂瑰紡鎵撳紑gz_dst
                gzFile gf = gzopen(gz_dst.c_str(),"wb");
                if(gf == NULL)
                {
                    printf("Compressfile[%s] gzopen failed!\n",src.c_str());
                    return false;
                }
                //3銆佸帇缂?
                size_t wlen = 0;//琛ㄧず宸茬粡鍘嬬缉浜嗙殑瀛楄妭鏁?
                while(wlen < buf.size())
                {
                    //鍙兘鍙槸鍘嬬缉閮ㄥ垎锛屾墍浠ヨ寰幆鍘嬬缉锛屾瘡娆″帇缂╃殑澶у皬鏈夊彉鍖?
                    size_t tmp_len = gzwrite(gf,&buf[wlen],buf.size()-wlen);
                    if(tmp_len == 0)
                    {
                        printf("Compressfile[%s] compress failed!\n",src.c_str());
                    }
                    //姣忔wlen閮藉姞涓婂凡缁忓帇缂╀簡鐨勯暱搴?
                    wlen += tmp_len;
                }
                gzclose(gf);
                return true;
            }
            static bool UnCompress(const std::string& gz_src,const std::string& dst)
            {
                //1銆佷互鍐欐墦寮€dst鏂囦欢
                std::ofstream ofs(dst,std::ios::binary);
                if(ofs.is_open() == false)
                {
                    printf("UnCompressfile[%s] open failed!\n",dst.c_str());
                    return false;
                }
                //2銆佷互gzopen鐨勮鏂瑰紡鎵撳紑gz_src
                gzFile gf = gzopen(gz_src.c_str(),"rb");
                if(gf == NULL)
                {
                    printf("UnCompressfile[%s] gzopen failed!\n",gz_src.c_str());
                    ofs.close();
                    return false;
                }
                //3銆佽В鍘嬬缉
                char buf[4096] = {0};//鍗曟瑙e帇鐨勫鍣?
                size_t ret = 0;//琛ㄧず鍗曟瑙e帇浜嗗灏?
                while((ret = gzread(gf,buf,sizeof(buf))) > 0)
                {
                    //澶勪簬鍐呭瓨鐨勮€冭檻锛屾瘡娆¤В鍘嬬缉buf澶у皬锛岀劧鍚庤杩涘幓
                    //杩欓噷涓嶇敤鑷繁鍐欑殑FileWrite鐨勫師鍥犳槸锛?
                    //鍥犱负姣忔鍙啓涓€閮ㄥ垎锛岃€屾瘡娆¤繘鍏ヨ繖涓嚱鏁板啓鐨勬椂鍊欓兘鏄粠寮€澶村紑濮嬪啓锛?
                    //浣嗘槸濡傛灉鎶婅嚜宸卞啓鐨勫嚱鏁版敼鎴愯拷鍔犳柟寮忕殑璇?浼氬鑷村悗鏈熶紶鍚屽悕鏂囦欢鐨勬椂鍊欎笉鏄鐩栬€屾槸杩藉姞
                    ofs.write(buf,ret);
                }
                ofs.close();
                gzclose(gf);
                return true;
            }
    };
}
FileNameManager.hpp  文件信息管理
#pragma once
#include<stdlib.h>
#include<vector>
#include<string>
#include<unistd.h>
#include<mysql/mysql.h>
#include<pthread.h>
#include<stdio.h>

#define MYSQL_IP "127.0.0.1"
#define MYSQL_USER "root"
#define MYSQL_PASSWD "1"
#define MYSQL_DB "file_name_db"
#define MYSQL_TB "file_gzfile_tb"

class Mysql
{
    public:
        //1銆乵ysql鍒濆鍖?
        static MYSQL* MysqlInit()
        {
            //鍒濆鍖杕ysql鐨勫彞鏌?
            MYSQL* mysql = NULL;
            mysql = mysql_init(NULL);
            if(mysql == NULL)
            {
                printf("mysql init error\n");
                return NULL;
            }
            //杩炴帴鏈嶅姟鍣?
            if(mysql_real_connect(mysql,MYSQL_IP,MYSQL_USER,MYSQL_PASSWD,MYSQL_DB,0,NULL,0) == NULL)
            {
                printf("connect mysql server failed : %s\n",mysql_error(mysql));
                MysqlDestroy(mysql);
                return NULL;
            }
            //璁剧疆瀹㈡埛绔瓧绗︾紪鐮侀泦
            if(mysql_set_character_set(mysql,"utf8") != 0)
            {
                printf("set mysql client character failed : %s\n",mysql_error(mysql));
                MysqlDestroy(mysql);
                return NULL;
            }
            return mysql;
        }

        //2銆乵ysql鎵ц璇彞
        static bool MysqlQuery(MYSQL* mysql, const std::string& sql)
        {
            int ret = mysql_query(mysql, sql.c_str());
            if(ret != 0)
            {
                printf("sql:[%s] query failed : %s\n",sql.c_str(),mysql_error(mysql)); 
                return false;
            }
            return true;
        }

        //3銆乵ysql閿€姣?
        static void MysqlDestroy(MYSQL* mysql)
        {
            if(mysql != NULL)
            {
                mysql_close(mysql);
            }
        }
};

class FileNameManager
{
    private:
        static FileNameManager _fnm;//鍗曚緥妯″紡
        MYSQL* _mysql;//瀛樺偍鏂囦欢鍚?鍘嬬缉鏂囦欢鍚嶇殑鏁版嵁搴?
        pthread_rwlock_t _rwlock;//淇濊瘉淇敼鏁版嵁搴撶殑鏂囦欢鍚嶆椂鍊欑殑瀹夊叏
    private:
        //涓轰簡闃叉瀯閫犮€佹嫹璐濄€佽祴鍊?
        FileNameManager()
        {
            pthread_rwlock_init(&_rwlock,NULL);
            //鍒濆鍖栬繛鎺ヤ竴涓暟鎹簱
            _mysql = Mysql::MysqlInit();
        }
        FileNameManager(const FileNameManager& fnm);
        FileNameManager& operator=(const FileNameManager& fnm);
    public:
        ~FileNameManager()
        {
            pthread_rwlock_destroy(&_rwlock);
            Mysql::MysqlDestroy(_mysql);
        }
        static FileNameManager* GetFNM()
        {
            return &_fnm;
        }
        //鍒ゆ柇鏂囦欢鏄惁瀛樺湪
        bool IsExist(const std::string& file_name)
        {
            pthread_rwlock_rdlock(&_rwlock);
            //1銆佽緭鍏ql鎸囦护
            char buf[4069] = {0};
            sprintf(buf,"select file_name from %s where file_name='%s';",MYSQL_TB,file_name.c_str());
            if(Mysql::MysqlQuery(_mysql,buf) == false)
            {
                pthread_rwlock_unlock(&_rwlock);
                return false;
            }
            //2銆佽幏鍙栨煡璇㈢粨鏋?
            MYSQL_RES* res = mysql_store_result(_mysql);
            if(res == NULL)
            {
                printf("IsExist get one file_name result failed : %s\n",mysql_error(_mysql));
                pthread_rwlock_unlock(&_rwlock);
                return false;
            }
            //3銆佸垽鏂粨鏋滄槸鍚﹀瓨鍦?
            int num_row = mysql_num_rows(res);
            if(num_row == 0)
            {
                //濡傛灉涓€鏉′篃娌℃壘鍒帮紝鍒欎笉瀛樺湪
                pthread_rwlock_unlock(&_rwlock);
                mysql_free_result(res);
                return false;
            }
            pthread_rwlock_unlock(&_rwlock);
            mysql_free_result(res);
            return true;
        }
        //鍒ゆ柇鏂囦欢鏄惁鍘嬬缉
        bool IsCompress(const std::string& file_name)
        {
            //1銆佽緭鍏ql鎸囦护:鎵緁ile_name鏂囦欢鐨刬s_compress淇℃伅
            pthread_rwlock_rdlock(&_rwlock);
            char buf[4069] = {0};
            sprintf(buf,"select is_compress from %s where file_name='%s';",MYSQL_TB,file_name.c_str());
            if(Mysql::MysqlQuery(_mysql,buf) == false)
            {
                pthread_rwlock_unlock(&_rwlock);
                return false;
            }
            //2銆佹煡璇㈢粨鏋?
            MYSQL_RES* res = mysql_store_result(_mysql);
            if(res == NULL)
            {
                printf("IsCompress get one file_name result failed : %s\n",mysql_error(_mysql));
                pthread_rwlock_unlock(&_rwlock);
                return false;
            }
            int num_row = mysql_num_rows(res);
            if(num_row != 1)
            {
                printf("IsCompress one file_name result error\n");
                pthread_rwlock_unlock(&_rwlock);
                mysql_free_result(res);
                return false;
            }
            //3銆佸垽鏂枃浠舵槸鍚﹀帇缂?
            MYSQL_ROW row = mysql_fetch_row(res);
            if(std::stoi(row[0]) == 1)
            {
                pthread_rwlock_unlock(&_rwlock);
                mysql_free_result(res);
                return true;
            }
            pthread_rwlock_unlock(&_rwlock);
            mysql_free_result(res);
            return false;
        }
        //鑾峰緱鏈帇缂╃殑鏂囦欢鍒楄〃
        bool GetUnCompressList(std::vector<std::string>* list)
        {
            //1銆佽緭鍏ql鎸囦护锛氭壘is_compress=0锛堟病鏈夊帇缂╋級鐨勬墍鏈変俊鎭?
            pthread_rwlock_rdlock(&_rwlock);
            char buf[4096] = {0};
            sprintf(buf,"select file_name from %s where is_compress=%d",MYSQL_TB,0);
            if(Mysql::MysqlQuery(_mysql,buf) == false)
            {
                pthread_rwlock_unlock(&_rwlock);
                return false;
            }
            //2銆佹煡璇㈢粨鏋?
            MYSQL_RES* res = mysql_store_result(_mysql);
            if(res == NULL)
            {
                printf("GetUnCompressList get one file_name result failed : %s\n",mysql_error(_mysql));
                pthread_rwlock_unlock(&_rwlock);
                return false;
            }
            //3銆佸皢鏌ヨ缁撴灉鎻掑叆list
            int num_rows = mysql_num_rows(res);
            for(int i = 0; i < num_rows; ++i)
            {
                //鑾峰彇姣忎竴鏉′俊鎭紙姣忎釜淇℃伅閲屽彧鏈変竴涓猣ile_name锛?
                MYSQL_ROW row = mysql_fetch_row(res);
                list->push_back(row[0]);
            }
            pthread_rwlock_unlock(&_rwlock);
            mysql_free_result(res);
            return true;
        }
        //鑾峰彇涓€涓枃浠剁殑鏃堕棿淇℃伅
        bool GetFileTime(const std::string& file_name, long* last_time)
        {
            //1銆佽緭鍏ql鎸囦护锛氳幏鍙杅ile_name鏂囦欢鐨刲ast_time淇℃伅
            pthread_rwlock_rdlock(&_rwlock);
            char buf[4069] = {0};
            sprintf(buf,"select last_time from %s where file_name='%s';",MYSQL_TB,file_name.c_str());
            if(Mysql::MysqlQuery(_mysql,buf) == false)
            {
                return false;
            }
            //2銆佹煡璇㈢粨鏋?
            MYSQL_RES* res = mysql_store_result(_mysql);
            if(res == NULL)
            {
                printf("GetFileTime get one file_name result failed : %s\n",mysql_error(_mysql));
                pthread_rwlock_unlock(&_rwlock);
                return false;
            }
            //3銆佸皢缁撴灉杩斿洖
            int num_rows = mysql_num_rows(res);
            if(num_rows != 1)
            {
                printf("IsCompress one file_name result error\n");
                pthread_rwlock_unlock(&_rwlock);
                mysql_free_result(res);
                return false;
            }
            MYSQL_ROW row = mysql_fetch_row(res);
            *last_time = std::stoi(row[0]);
            pthread_rwlock_unlock(&_rwlock);
            mysql_free_result(res);
            return true;
        }
        //鑾峰彇鎵€鏈夋枃浠跺悕
        bool GetAllFile(std::vector<std::string>* list)
        {
            //1銆佽緭鍏ql鎸囦护锛氳幏鍙栨墍鏈夋枃浠剁殑鏂囦欢鍚?
            pthread_rwlock_rdlock(&_rwlock);
            char buf[4096] = {0};
            sprintf(buf,"select file_name from %s;",MYSQL_TB);
            if(Mysql::MysqlQuery(_mysql,buf) == false)
            {
                return false;
            }
            //2銆佹煡璇㈢粨鏋?
            MYSQL_RES* res = mysql_store_result(_mysql);
            if(res == NULL)
            {
                printf("GetAllFile get one file_name result failed : %s\n",mysql_error(_mysql));
                pthread_rwlock_unlock(&_rwlock);
                return false;
            }
            //3銆佸皢鏌ヨ缁撴灉鎻掑叆list
            int num_rows = mysql_num_rows(res);
            for(int i = 0; i < num_rows; ++i)
            {
                //鑾峰彇姣忎竴鏉′俊鎭紙涓€鏉′俊鎭彧鏈変竴涓猣ile_name锛?
                MYSQL_ROW row = mysql_fetch_row(res);
                list->push_back(row[0]);
            }
            pthread_rwlock_unlock(&_rwlock);
            mysql_free_result(res);
            return true;
        }
        //鎻掑叆涓€涓枃浠朵俊鎭?
        bool Insert(const std::string& file_name)
        {
            pthread_rwlock_wrlock(&_rwlock);
            char buf[4096] = {0};
            //鎻掑叆涓や釜file_name銆佹湭鍘嬬缉銆佸瓨鏀炬椂闂?
            time_t now_time = time(NULL);
            sprintf(buf,"insert into %s(file_name,is_compress,last_time) values('%s',%d,%ld);",
                    MYSQL_TB,file_name.c_str(),0,now_time);
            if(Mysql::MysqlQuery(_mysql,buf) == false)
            {
                pthread_rwlock_unlock(&_rwlock);
                return false;
            }
            pthread_rwlock_unlock(&_rwlock);
            return true;
        }
        //鏇存柊涓€涓枃浠剁殑鏂囦欢淇℃伅 
        bool Update(const std::string& file_name, int is_compress)
        {
            //1銆佽緭鍏ql鎸囦护锛氭洿鏂版枃浠朵俊鎭紙涓昏鏄寚锛氭洿鏂癷s_compress銆乴ast_time锛?
            pthread_rwlock_wrlock(&_rwlock);
            char buf[4096] = {0};
            time_t now_time = time(NULL);
            sprintf(buf,"update %s set is_compress=%d, last_time=%ld where file_name='%s'",
                    MYSQL_TB,is_compress,now_time,file_name.c_str());
            if(Mysql::MysqlQuery(_mysql,buf) == false)
            {
                pthread_rwlock_unlock(&_rwlock);
                return false;
            }
            pthread_rwlock_unlock(&_rwlock);
            return true;
        }
};
FileNameManager FileNameManager::_fnm;//鍗曚緥妯″紡
NotHotCompress.hpp  非热点文件压缩
#pragma once
#include"FileNameManager.hpp"
#include"FileTool.hpp"
#include<time.h>
#include<pthread.h>

#define COMMON_FILE_DIR "./common_file/"//鍘熸枃浠跺瓨鏀剧殑鏂囦欢澶?
#define GZ_FILE_DIR "./gz_file/"//鍘嬬缉鏂囦欢瀛樻斁鐨勬枃浠跺す
#define CHECK_TIME 30//妫€鏌ョ殑闂撮殧鏃堕棿
#define NOT_HOT_TIME 15//闈炵儹鐐规枃浠剁殑鏃堕棿鏍囧噯

class NotHotCompress
{
    private:
        std::string _co_file_dir;
        std::string _gz_file_dir;
    public:
        NotHotCompress(const std::string& co_file_dir = COMMON_FILE_DIR, const std::string& gz_file_dir = GZ_FILE_DIR)
            :_co_file_dir(co_file_dir)
             ,_gz_file_dir(gz_file_dir)
        {  }
        bool Start()
        {
            while(1)
            {
                //1銆佹嬁鍒版墍鏈夋湭鍘嬬缉鐨勬枃浠跺悕
                std::vector<std::string> list;
                //2銆佸惊鐜殑鍒ゆ柇姣忎竴涓枃浠舵槸鍚︽槸鐑偣鏂囦欢锛岀劧鍚庤繘琛屽帇缂?
                FileNameManager::GetFNM()->GetUnCompressList(&list);
                for(const auto& file_name:list)
                {
                    //濡傛灉鏄儹鐐规枃浠跺垯涓嶉渶瑕佸帇缂?
                    if(IsHot(file_name) == true)
                    {
                        continue;
                    }
                    //鎷垮埌甯﹁矾寰勭殑鍘熸枃浠剁殑鏂囦欢鍚嶃€佸甫璺緞鐨勫帇缂╂枃浠剁殑鏂囦欢鍚?
                    std::string gzfile_name = file_name + ".gz";
                    std::string path_gzfile_name = _gz_file_dir + gzfile_name;
                    std::string path_file_name = _co_file_dir + file_name;
                    //濡傛灉鍘嬬缉澶辫触鍒欒烦杩囥€備絾骞朵笉琛ㄧず涓嶅帇缂╀簡锛屽洜涓轰笅娆hile寰幆杩樹細鍥炴潵鐨?
                    if(FileTool::FileCompressTool::Compress(path_file_name,path_gzfile_name) == false)
                    {
                        continue;
                    }
                    //鏇存柊鏁版嵁搴撶殑鏂囦欢淇℃伅
                    FileNameManager::GetFNM()->Update(file_name,1);
                    //鍒犻櫎鍘熸枃浠讹紝鍙繚鐣欏帇缂╂枃浠?
                    unlink(path_file_name.c_str());
                }
                //姣忛殧涓€瀹氱殑鏃堕棿妫€鏌ヤ竴娆?
                sleep(CHECK_TIME);
            }
        }
        bool IsHot(const std::string& file_name)
        {
            long last_time;
            FileNameManager::GetFNM()->GetFileTime(file_name,&last_time); 
            time_t now_time = time(NULL);
            if(now_time - last_time > NOT_HOT_TIME)
            {
                return false;
            }
            return true;
        }
};
HttpServer.hpp  http服务器
#pragma once
#include<sys/stat.h>
#include<pthread.h>
#include"FileNameManager.hpp"
#include"FileTool.hpp"
#include"httplib.h"

#define COMMON_FILE_DIR "./common_file/"//鍘熸枃浠跺瓨鏀剧殑鏂囦欢澶?
#define GZ_FILE_DIR "./gz_file/"//鍘嬬缉鏂囦欢瀛樻斁鐨勬枃浠跺す
#define SERVER_IP "0.0.0.0"
#define SERVER_PORT 4418

class Server
{
    private:
        httplib::Server _server;
    public:
        void Start()
        {
            //涓婁紶鏂囦欢
            _server.Put("/(.*)",Upload);
            //璇锋眰鏂囦欢鍒楄〃
            _server.Get("/list",List);
            //涓嬭浇鏂囦欢
            _server.Get("/download/(.*)",Download);
            //鐩戝惉杩炴帴
            _server.listen(SERVER_IP,SERVER_PORT);
        }
    private:
        static void Upload(const httplib::Request& request, httplib::Response& response) 
        {
            //1銆佹嬁鍒板甫璺緞鐨勬枃浠跺悕
            std::string file_name = request.matches[1];
            std::string path_file_name = COMMON_FILE_DIR + file_name;
            printf("file[%s] is upload ... ...\n",file_name.c_str());
            //2銆佹洿鏂版暟鎹簱閲岀殑鏂囦欢淇℃伅
            //濡傛灉鏂囦欢涓嶅瓨鍦ㄥ垯鎻掑叆鏂囦欢淇℃伅
            if(FileNameManager::GetFNM()->IsExist(file_name) == false)
            {
                FileNameManager::GetFNM()->Insert(file_name);
            }
            //濡傛灉鏂囦欢瀛樺湪锛屽皢涔嬪墠鐨勬枃浠跺垹闄わ紝鐒跺悗浼犲叆鏂扮殑鏂囦欢锛屾洿鏂颁俊鎭?
            else
            {
                //濡傛灉涔嬪墠鐨勬枃浠惰鍘嬬缉浜嗭紝鍒欏垹闄ゅ帇缂╁寘
                if(FileNameManager::GetFNM()->IsCompress(file_name) == true)
                {
                    std::string path_gzfile_name = GZ_FILE_DIR + file_name + ".gz";
                    unlink(path_gzfile_name.c_str());
                }
                //濡傛灉涔嬪墠鐨勬枃浠惰繕娌¤鍘嬬缉锛屽垯鍒犻櫎鍘熸枃浠?
                else
                {
                    unlink(path_file_name.c_str());
                }
                FileNameManager::GetFNM()->Update(file_name,0);
            }
            //3銆佸皢鏂囦欢鏁版嵁鍐欏叆鍒扮鐩橀噷
            FileTool::FileReadWriteTool::FileWrite(path_file_name,request.body);
            //4銆佽缃姸鎬佺爜
            response.status = 200;
            printf("file[%s] upload success!\n",file_name.c_str());
        }
        static void List(const httplib::Request& request, httplib::Response& response)
        {
            (void)request;
            //1銆佽幏鍙栨墍鏈夋枃浠跺悕鐨勫垪琛?
            std::vector<std::string> list;
            FileNameManager::GetFNM()->GetAllFile(&list); 
            //2銆侀亶鍘嗚繖涓垪琛紝鎶婃墍鏈夋枃浠跺悕鍜屽搴旂殑涓嬭浇閾炬帴濉叆鍒版鏂囦腑
            std::stringstream tmp; 
            tmp << "<html><body><hr />";
            for(const auto& file_name:list)
            {
                //绗竴涓猣ile_name鏄姹備笅杞絝ile_name鐨勮姹傛柟娉曪紝鍙偣鍑绘潵鍙戦€佽姹傘€傜浜屼釜鏄睍绀洪〉闈㈢殑鏂囦欢鍚?
                tmp << "<a href='/download/" << file_name << "'>" << file_name << "</a>";
                tmp << "<hr />";
            }
            tmp << "<hr /></body><html>";
            //3銆佽缃簲绛旀鏂囦俊鎭拰鐘舵€佺爜
            response.set_content(tmp.str().c_str(),tmp.str().size(),"text/html");
            response.status = 200;
        }
        static void Download(const httplib::Request& request, httplib::Response& response)
        {
            //1銆佽幏鍙栬涓嬭浇鐨勬枃浠跺悕
            std::string file_name = request.matches[1];
            std::string path_file_name = COMMON_FILE_DIR + file_name;
            //2銆佹鏌ユ枃浠舵槸鍚﹀瓨鍦?
            if(FileNameManager::GetFNM()->IsExist(file_name) == false)
            {
                response.status = 404;
                return;
            }
            //3銆佹鏌ユ枃浠舵槸鍚﹀凡鍘嬬缉
            if(FileNameManager::GetFNM()->IsCompress(file_name) == true)
            {
                //濡傛灉鍘嬬缉浜嗗氨瑙e帇缂?
                std::string path_gzfile_name = GZ_FILE_DIR + file_name + ".gz";
                FileTool::FileCompressTool::UnCompress(path_gzfile_name,path_file_name);
                //瑙e帇瀹屽垹闄ゅ帇缂╁寘
                unlink(path_gzfile_name.c_str());
                //鍒犻櫎浜嗚В鍘嬪寘鍚庢洿鏂版枃浠剁殑淇℃伅(0琛ㄧず鏈帇缂?
                FileNameManager::GetFNM()->Update(file_name,0);
            }
            //4銆佸皢瀹㈡埛绔涓嬭浇鐨勬枃浠舵暟鎹啓鍒皉esponse鐨勬鏂囬噷
            FileTool::FileReadWriteTool::FileRead(path_file_name,&response.body);
            response.set_header("content-type","application/octet-stream");//浜岃繘鍒舵祦涓嬭浇
            response.status = 200;
        }
};

客户端

MD5.hpp  MD5值计算
#pragma once
#include<string>
#include<math.h>
#include<iostream>
#include<fstream>
#define CHUNK_BYTE 64
typedef unsigned int uint_32;

class MD5
{
public:
	MD5()
	{
		init();
	}

	//初始化:_k[64],和调用reset重置
	void init()
	{
		// 因为此处i从0开始,所以需要sin(i + 1)
		for (int i = 0; i < 64; ++i)
		{
			_k[i] = (uint_32)(abs(sin(i + 1.0))*pow(2.0, 32));
		}
		reset();
	}

	//重置:_a,_b,_c,_d
	void reset()
	{
		_a = 0x67452301;
		_b = 0xefcdab89;
		_c = 0x98badcfe;
		_d = 0x10325476;
		memset(_chunk, 0, CHUNK_BYTE);
		_lastByte = 0;
		_totalByte = 0;
	}

	//位运算函数 F G H I
	uint_32 F(uint_32 b, uint_32 c, uint_32 d)
	{
		return (b & c) | ((~b) & d);
	}
	uint_32 G(uint_32 b, uint_32 c, uint_32 d)
	{
		return (b & d) | (c & (~d));
	}
	uint_32 H(uint_32 b, uint_32 c, uint_32 d)
	{
		return b ^ c ^ d;
	}
	uint_32 I(uint_32 b, uint_32 c, uint_32 d)
	{
		return  c ^ (b | (~d));
	}

	//循环左移
	uint_32 left_shift(uint_32 number, int shiftNumber)
	{
		return ((number << shiftNumber) | (number >> (32 - shiftNumber)));
	}

	//计算一个数据块chunk的MD5值
	void cal_MD5(uint_32* chunk)
	{
		int a = _a;
		int b = _b;
		int c = _c;
		int d = _d;
		int tmp_4;
		int g;
		//将这个数据块一共处理4轮,每轮处理16次,每次处理四个字节
		for (int i = 0; i < 64; ++i)
		{
			if (i >= 0 && i <= 15)
			{
				g = i;
				tmp_4 = F(b, c, d);
			}
			else if (i >= 16 && i <= 31)
			{
				g = (5 * i + 1) % 16;
				tmp_4 = G(b, c, d);
			}
			else if (i >= 32 && i <= 47)
			{
				g = (3 * i + 5) % 16;
				tmp_4 = H(b, c, d);
			}
			else
			{
				g = (7 * i) % 16;
				tmp_4 = I(b, c, d);
			}

			int tmp_d = d;
			d = c;
			c = b;
			b = b + left_shift((a + tmp_4 + _k[i] + chunk[g]), _leftShift[i]);
			a = tmp_d;
		}
		//更新四个值
		_a += a;
		_b += b;
		_c += c;
		_d += d;
	}

	//给最后一个块数据添加填充位和比特位长度
	void cal_finalMD5()
	{
		char* p = _chunk + _lastByte;
		//填充冗余信息(最少填充一个字节,因为文件里最小单位是一个字节)
		*p++ = (char)0x80;
		int remain_byte = CHUNK_BYTE - _lastByte - 1;
		if (remain_byte < 8)
		{//如果后面不够64比特位(8字节),就把剩余的填0,再建立一个64字节(512比特)的数据块,
			//在新的数据块的最后64比特位填文件的比特长度,之前填写0

			memset(p, 0, remain_byte);
			//填满一个数据块就处理
			cal_MD5((uint_32*)_chunk);
			//创建新的数据块,先填0,后面的语句统一填写文件比特长度
			memset(_chunk, 0, CHUNK_BYTE);
		}
		else
		{//如果后面够64比特位(8字节),就直接填充0(后面的语句统一填写文件比特长度)

			memset(p, 0, remain_byte);
		}
		//将文件大小的单位转成bit
		unsigned long long totalbits = _totalByte * 8;
		//强转的目的是:在使用数组的时候,偏移一下偏移8个字节,刚好符合最后的64比特位(8字节)
		((unsigned long long*)_chunk)[7] = totalbits;
		cal_MD5((uint_32*)_chunk);
	}

	//把abcd转成最后的MD5值
	std::string Hex_change_MD5(uint_32 num)
	{
		//通过数字的映射来将字符添加到输出的字符串
		const std::string strMap = "0123456789abcdef";
		std::string ans;
		for (int i = 0; i < 4; ++i)
		{
			//每次循环都右移,获得最后八位的数字
			int tmp = ((num >> (i * 8)) & 0xff);
			//将数字转成字符
			ans += strMap[tmp / 16];
			ans += strMap[tmp % 16];
		}
		return ans;
	}

	std::string getStringMD5(const std::string& str)
	{
		if (str.empty())
		{
			//如果为空,直接返回空的MD5值
			return Hex_change_MD5(_a).append(Hex_change_MD5(_b)).append(Hex_change_MD5(_c)).append(Hex_change_MD5(_d));
		}
		_totalByte = str.size();
		//计算文件的数据块的数量
		uint_32 chunk_nums = _totalByte / CHUNK_BYTE;
		//最后一块不足8字节的数据块
		_lastByte = _totalByte % CHUNK_BYTE;
		const char* p = str.c_str();
		//计算所有完整的数据块
		for (int i = 0; i < (int)chunk_nums; ++i)
		{
			//每次考8个字节(一个数据块)然后计算
			memcpy(_chunk, p + (i*CHUNK_BYTE), CHUNK_BYTE);
			cal_MD5((uint_32*)_chunk);
		}
		//将最后一块拷贝,然后计算
		memcpy(_chunk, p + (CHUNK_BYTE*chunk_nums), _lastByte);
		cal_finalMD5();
		return Hex_change_MD5(_a).append(Hex_change_MD5(_b)).append(Hex_change_MD5(_c)).append(Hex_change_MD5(_d));
	}
	std::string getFileMD5(const char* filePath)
	{
		std::ifstream in_file(filePath, std::ifstream::binary);
		if (!in_file.is_open())
		{
			perror("file open failed!");

		}
		while (!in_file.eof())
		{
			//整体全部读取。以空间换时间
			/*in_file.seekg(0, in_file.end);
			int length = in_file.tellg();
			in_file.seekg(0, in_file.beg);
			char* totalData = new char[length];
			in_file.read(totalData, length);*/
			in_file.read(_chunk, CHUNK_BYTE);
			if (in_file.gcount() != CHUNK_BYTE)
			{
				break;
			}
			_totalByte += CHUNK_BYTE;
			cal_MD5((uint_32*)_chunk);
		}
		_lastByte = in_file.gcount();
		_totalByte += in_file.gcount();
		cal_finalMD5();

		return Hex_change_MD5(_a).append(Hex_change_MD5(_b)).append(Hex_change_MD5(_c)).append(Hex_change_MD5(_d));
	}
private:

	//循环左移的数组
	static int _leftShift[64];
	//_k[i] = floor(2^(32) * abs(sin(i + 1))) 
	uint_32 _k[64];
	//数据块(64byte)
	char _chunk[CHUNK_BYTE];
	//最后一块数据的字节数
	uint_32 _lastByte;
	//总字节数
	long long _totalByte;
	//MD5的buffer
	uint_32 _a;
	uint_32 _b;
	uint_32 _c;
	uint_32 _d;
};
int MD5::_leftShift[64] = { 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 7, 12, 17, 22, 5, 9, 14, 20, 5, 9, 14,
						20, 5, 9, 14, 20, 5, 9, 14, 20, 4, 11, 16, 23, 4, 11, 16, 23, 4, 11, 16, 23,
						4, 11, 16, 23, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21, 6, 10, 15, 21 };
FIleTool.hpp  文件工具
#pragma once
#include<string>
#include<fstream>
#include<boost/filesystem.hpp>
#include"MD5.hpp"

//文件的工具类(读,写)
class FileTool
{
public:
	//从文件中读取内容
	static bool Read(const std::string& name, std::string* body)
	{
		std::ifstream ifs(name, std::ios::binary);
		if (ifs.is_open() == false)
		{
			printf("file %s open failed!\n", name.c_str());
			return false;
		}
		//得到该文件的大小
		int64_t fsize = boost::filesystem::file_size(name);
		printf("这个[%s]文件的大小是:.............%d\n", name.c_str(),fsize);
		//将要接受的容器扩容
		body->resize(fsize);
		ifs.read(&((*body)[0]), fsize);
		//判断上次操作是否成功
		if (ifs.good() == false)
		{
			printf("file %s read failed!\n", name.c_str());
			return false;
		}
		ifs.close();
		return true;
	}
	static bool Write(const std::string& name, const std::string& body)
	{
		std::ofstream ofs(name, std::ios::binary);
		if (ofs.is_open() == false)
		{
			printf("Write : file %s open failed!\n", name.c_str());
			return false;
		}
		ofs.write(&body[0], body.size());
		if (ofs.good() == false)
		{
			printf("file %s write failed!\n", name.c_str());
			return false;
		}
		ofs.close();
		return true;
	}
	static void CalFileMd5(const std::string& path_file_name, std::string* file_md5)
	{
		MD5 md5;
		*file_md5 = md5.getFileMD5(path_file_name.c_str());
	}
};
File_Info_Manager.hpp  文件信息管理
#pragma once
#include<string>
#include<unordered_map>
#include<vector>
#include<sstream>
#include<boost/algorithm/string.hpp>

#include"FIleTool.hpp"

class FileInfoManager
{
private:
	std::string _store_file;//磁盘里存储文件信息的文件
	std::unordered_map<std::string, std::string> _file_info_list;//备份文件信息的容器
public:
	FileInfoManager(const std::string& file_name)
		:_store_file(file_name)
	{
		InitLoad();
	}
	//将文件信息存储到磁盘文件里
	void Storage()
	{
		std::stringstream ss;
		for (auto& file_info : _file_info_list)
		{
			//各个文件间用/r/n来分隔,每一个文件的 文件名和md5值 用空格分隔
			ss << file_info.first << " " << file_info.second << "\r\n";
		}
		FileTool::Write(_store_file, ss.str());

	}
	//将磁盘文件里读取文件信息到内存
	bool InitLoad()
	{
		//1、先读出文件内容
		std::string buf;
		if (FileTool::Read(_store_file, &buf) == false)
		{
			return false;
		}
		//2、用boost库来分割内容
		//a.先分割各个文件
		std::vector<std::string> list;
		boost::algorithm::split(list, buf, boost::is_any_of("\r\n"), boost::token_compress_off);
		//b.在list里分割文件名和MD5
		for (const auto& file_info : list)
		{
			size_t pos = file_info.find(" ");
			std::string file_name = file_info.substr(0, pos);
			//pos+1是因为空格不算
			std::string file_md5 = file_info.substr(pos + 1);
			//将文件名和md5值读到列表里
			_file_info_list[file_name] = file_md5;
		}
		return true;
	}
	//拿到一个文件的md5值
	bool GetMd5(const std::string& file_name,std::string* file_md5)
	{
		auto it = _file_info_list.find(file_name);
		if (it == _file_info_list.end())
		{
			return false;
		}
		*file_md5 = it->second;
		return true;
	}
	//插入/更新一个文件信息
	void Insert(const std::string& file_name, const std::string& file_md5)
	{
		_file_info_list[file_name] = file_md5;
		Storage();
	}
};
Cloud_Backup_Cilent.hpp  http客户端
#pragma once
#include<string>
#include<utility>
#include"httplib.h"
#include"File_Info_Manager.hpp"

class CloudBackupClient
{
private:
	std::string _svr_ip;
	uint16_t _svr_port;
	FileInfoManager _fim;
	std::string _listen_dir;//在磁盘中的文件夹(要备份的文件就放里面)
public:
	CloudBackupClient(const std::string& listen_file, const std::string& file_info_file, const std::string& svr_ip, uint16_t svr_port)
		:_listen_dir(listen_file)
		, _fim(file_info_file)
		, _svr_ip(svr_ip)
		, _svr_port(svr_port)
	{
		//若文件夹不存在则创建
		if (boost::filesystem::exists(_listen_dir) == false)
		{
			boost::filesystem::create_directory(_listen_dir);
		}
	}

	void Start()
	{
		//将客户端连接服务端
		httplib::Client client(_svr_ip.c_str(), _svr_port);
		while (1)
		{
			printf("开始云备份... ...\n");
			//一、先获取需要压缩的文件列表
			std::unordered_map<std::string, std::string> list;
			GetNeedBackupList(&list, _listen_dir);
			//二、将所有要上传的文件上传
			for (const auto& file : list)
			{
				//1、拿到文件名和带路径的文件名
				//文件名是为了用于request
				//带路径的文件名是为了读取文件数据
				std::string path_file_name = file.first;
				std::string file_name = file.second;
				printf(" %s is need to backup ...\n", file_name.c_str());
				//2、将文件数据读出
				std::string buf;
				FileTool::Read(path_file_name, &buf);
				printf("buf的大小是:%d\n", buf.size());
				//3、发送数据
				std::string request_path = "/" + file_name;
				auto rsp = client.Put(request_path.c_str(), buf, "application/octet-stream");//以二进制流文件下载
				if (rsp == NULL || (rsp->status != 200))
				{
					printf(" %s backup failed!\n", file_name.c_str());
					continue;
				}
				//4、更新文件信息列表
				std::string file_md5;
				FileTool::CalFileMd5(path_file_name, &file_md5);
				_fim.Insert(file_name, file_md5);
				printf(" %s backup success!\n", file_name.c_str());
			}
			printf("此次扫描完毕... ...\n");
			Sleep(10000);
		}
	}
private:
	void GetNeedBackupList(std::unordered_map<std::string,std::string>* list, std::string file_path)
	{
		//扫描文件夹下哪些文件需要备份:
		//1、若文件夹里的某些文件 在 存文件信息的文件里没有找到,表示这个文件是新加进去的
		//2、若文件夹里的某个文件的md5值 和 对应存文件信息的文件的md5值不一样,表示更新过
		printf("在扫盘... ...\n");
		boost::filesystem::directory_iterator begin(file_path);
		boost::filesystem::directory_iterator end;
		for (; begin != end; ++begin)
		{
			//如果是文件夹则进入文件夹
			if (boost::filesystem::is_directory(begin->status()) == true)
			{
				//如果是当前文件或者上级文件则跳过
				if (begin->path().string() != "." || begin->path().string() != "..")
				{
					//如果是普通文件夹则递归的进入这个函数继续扫描,直到扫描到文件
					std::string depth_path = begin->path().string();
					GetNeedBackupList(list, depth_path);
				}
			}
			else 
			{
				//a.先计算这个文件的md5值
				std::string new_md5;
				std::string path_file_name = begin->path().string();
				FileTool::CalFileMd5(path_file_name, &new_md5);
				//b.在拿到之前(最后一次上传的时候)这个文件的md5值
				std::string old_md5;
				std::string file_name = begin->path().filename().string();
				_fim.GetMd5(file_name, &old_md5);
				//比较两个md5,不同则需要备份
				//ps:如果_fim.GetMd5(file_name, &old_md5);里找不到,则old_md5会是空,则表示new_md5是新放进去的文件
				if (old_md5 != new_md5)
				{
					list->insert(std::make_pair(path_file_name, file_name));
				}
			}
		}
	}
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值