云备份项目
目录
使用方法:
项目实现目标设计:
服务端程序: 部署在linux服务器上
实现针对客户端请求的业务处理: 文件的上传备份,以及客户端浏览器的查看以及下载功能, 并且具有热点管理功能和下载时断点续传功能,将非热点文件压缩存储节省磁盘空间
服务端模块划分设计:
网络通信模块: 实现与客户端进行网络通信,并进行http协议数据解析客户端请求
业务处理模块: 明确客户端请求,并且进行对应的业务处理(上传文件,备份信息获取,文件下载)数据管理模块: 对备份文件信息进行统一数据管理
热点管理模块: 对服务器上备份的文件进行热点管理,将非热点文件进行压缩存储
客户端程序: 部署在windows客户机上
实现针对客户端主机上指定文件夹中的文件,自动进行检测判断是否需要备份,需要则上传到服务器备份
客户端模块划分设计:
目录检测模块: 遍历客户端主机上的指定文件夹,获取文件夹下所有的文件信息
数据管理模块:管理客户端所备份的所有文件信息(判断一个文件是否需要备份:1.历史备份信息中不存在,2.历史备份信息存在但是不一致)
网络通信模块: 搭建网络通信客户端,将需需要备份的文件上传备份到服务器
项目环境:
服务器: centos7(以上)/ vim、g++(7.3以上)、gdb、makefile
客户端: Windows10/ vs2017(以上)
使用到的库:
Jsoncpp 库:
json 是一种数据交换格式,采用完全独立于编程语言的文本格式来存储和表示数据。比如将一个一个的键值对组织成这种格式:

jsoncpp 库用于实现 json 格式的序列化和反序列化,完成将多个数据对象组织成为 json 格式字符串,以及将 json 格式字符串解析得到多个数据对象的功能。
在本项目中, 选择json序列化在服务端的数据管理模块实现数据的持续化存储和数据传输, 管理文件信息时将文件信息组织成json序列化后存储在备份文件信息文件中实现持续化存储. 读取备份文件信息时再将信息反序列化放入hash表中实现运行时高效地内存读写.
使用方法:

Bundle 库:
数据压缩及解压缩库, 在本项目中服务端的热点管理模块, 若判断文件不是热点文件使用bundle库将其压缩用户下载时再将其解压缩.(因为bundle.cpp过于庞大, 编译较慢 在项目中将其生成为一个静态库使用)
使用方法:

Httplib 库:
本来应该自己实现一个基本的http服务器, 但是本项目由于时间较为紧张, 因此使用了现成的高效的httplib库.
在本项目中, 服务端使用httplib库实现业务处理模块, 处理客户端发来的上传, 查看, 下载三个请求, 客户端使用其实现文件的上传.(客户端的查看, 下载请求通过浏览器直接输入实现). 在传输层是基于tcp实现传输的
使用方法:


C++17 filesystem库:
使用该库实现服务端和客户端的文件操作工具类实现( util.hpp ), 实现文件目录的创建, 检索文件是否存在, 获取不带路径的纯文件名, 浏览遍历指定目录下文件, 删除文件等操作.
使用方法: 详见 util.hpp...
模块的实现:
服务端数据管理模块:
要管理的数据: 原文件名,原文件大小,原文件时间属性,对应的压缩包名称,压缩标志
上传的文件,最终是用户在浏览器上进行查看并下载的,而浏览器界面上需要能够展示客户端曾经备份过的文件: 原文件名,文件大小,文件备份时间
在浏览器客户端下载时, 规定下载链接为/download/文件名 , 所以我们管理的数据中还需要有url_path这个下载链接, 且url_path作为运行时内存中数据hash表的key值, 其他信息封装为fileinfo结构体作hash的val值.

实现详解( data.hpp & util.hpp):
文件数据的管理主要是文件信息的增删改查, 和每次对信息增改删后更新back_info文件中的备份文件信息:
增: 传入一个文件的文件名(带路径), 先通过文件工具类File_util判断文件是否存在, 存在才获取文件信息并压入hash表写入back_info, 没有该文件则说明该文件不在服务端存储备份文件的文件夹backup_dir中, 视为没有备份该文件不增加输入的文件名的文件信息.
删: 删除backup_info中备份文件的信息.. 通常是不需要这么干的. 除非是服务端自己调用File_util中的文件删除接口把用户备份的文件删除了, 才应该把被删除的备份文件的文件信息也给删除了.
改: 给出文件名和要修改的文件状态(bool类型), 备份文件被压缩后更新备份信息, 只需要更新文件的压缩状态为true, 因为其他的信息都是固定不变的. 用户下载非热点的压缩文件时解压缩后更新压缩状态为false.
查: 查询与有三种方法. 1.查询所有文件的备份信息, 传入一个vecrtor<fileinfo>接收所有文件的信息
2. 根据指定文件的url_path查询, 传入url_path和fileinfo结构体指针接收指定文件的文件信息
3. 根据指定文件的real_path查询, 传入real_path和fileinfo结构体指针接收指定文件的文件信息
文件File_util主要是通过C++17中的文件系统库以ifstream及ofsram类操作文件, 包括文件的读写(open接口不存在则会创建), 判断文件是否存在, 获取不带路径的纯文件名, 浏览遍历指定目录下文件, 删除文件操作, 除此之外, 文件的压缩解压缩也属于文件操作也放在File_util内.
util.hpp内除了File_util还有Json_util类, 有序列化反序列化两个静态方法, 静态方法好处是不需要实例化这个类的对象就可以对文件信息序列化反序列化.
服务端热点管理模块:
功能: 对备份目录下的文件,进行检测,判断每个文件是否为热点文件, 本项目设的判断依据是当前时间距离文件最后一次访问时间是否超过30秒 热点判断时间,如果超过了就表示这是一个非热点文件,则需要压缩存储,压缩之后删除源文件,压缩成功之后,通过数据管理对象,修改备份信息, 状态为已压缩。
实现:
1.遍历指定目录-文件备份目录-源文件存储路径,获取目录下所有文件的实际路径名
2.遍历所有文件名,通过文件路径名,获取文件的时间属性(最后一次访问时间)
3.获取系统当前时间,与文件最后一次访问时间进行相减,将差值与指定的热点判断时间进行比较
4.若超过热点判断时长,则判定为非热点,进行压缩存储
5.压缩完毕后,修改备份信息
服务端网络通信模块 与 业务处理模块:
网络通信模块使用httplib库搭建http服务器,我们更多关注业务的处理。业务处理包括: 上传的处理,查看页面请求的处理,下载的处理. ( 文件上传成功后,测试代码时需要校验校验源文件与服务器备份的文件是否一致, 通过比较MD5值判断文件内容是否一致)
MD5: 根据文件内容进行的散列计算得到的一串字符串,文件内容只要不同计算而出的MD5值就不一样. linux命令: md5sum a.txt windows命令: certutil-hashfile a.txt md5
上传: 上传请求业务处理通过服务器接收到的 http 请求正文实现, 通过 MutipartFormData 中的区域标识符字段name是否为 "file" 判断上传的是否为文件, 再通过 filename 文件名字段获取备份文件名, content字段获取备份文件的内容, 通过 File_util 中的 write 接口实现备份, 添加备份信息, 成功返回 200OK 响应状态码.
查看: 查看请求业务处理通过遍历获取备份文件夹下所有备份文件的文件信息, 将文件信息组织渲染成html页面, 这里简单学了最基本的前端知识来写了个页面, 将文件名作为 /download/filename 超链接, 客户端浏览器点击该文件名即向服务点发送 /download/filename 下载文件请求. 将写好的html 页面作为响应正文返回.
下载: 下载请求业务具有断点续传功能, 断点续传: 当下载一个文件的时候,下载到中途,因为网络或者其他原因,导致下载中断, 如果第二次后边重新下载所有文件数据,效率就比较低,因为实际上之前传输的数据是不需要重新传输的如果这次下载只是从上一次断开的位置重新下载,就可以提升很高的下载效率.
实现思想(配合代码注释): 需要一端能够记记录目己传输的位置,也就是下载到哪里了,但是服务端记录是不合适的,因为请求都是由客户端发起的,因此应该由客户端记录实际上也就是谁需要数据,就谁记录. 客户端下载过程中,记录自己的已经下载的数据长度和位置,如果中途下载中断,在下次请求的时候,将自己所需要的指定文件的区间范围发送给服务器,服务器根据指定的区间范围返回数据即可。但是存在一个问题: 怎么保证上次下载的文件,和当前续传的文件是一致的?也就是如果续传的时候,服务器上的文件数据已经发生了改变,则续传是没有意义的. 因此就必须有个标识(etag),用于辨别服务器上的文件在上一次下载后有没有被修改过。没有修改过则可以断点续传,如果修改过了则不能断点续传,需要重新下载
因此断点续传,需要和上一次的下载关联起来,需要上一次下载信息的支持,比如文件唯一标识

客户端数据管理模块:
客户端的数据处理模块不需要管理太多的文件数据, 只需要记录 "文件名= etag" 即可, 所以不需要json库的将信息序列化存储也就不需要按序列化这两个接口了, 当然客户端也不需要压缩解压缩接口. File_util 的代码是linux, windows都可以复用的, 所以获取文件信息, 创建备份文件目录, 创建备份文件信息文件等直接复用 util.hpp 代码即可. 客户端的数据管理模块也是对文件信息的增删改查,
增: 将文件放入上传备份的文件夹中, 则增加放入的文件的文件信息.
删: 和服务端一样, 作为拓展模块, 一般不需要调用删除.
改: 在判断文件是否需要上传时, 若此时文件信息和之前保存发文件信息不同了则上传, 同时更新文件存储信息.
查: 通过查询保存的文件信息etag与当前时刻的文件etag比较时使用.
客户端目录检查模块:
客户端的目录检索模块复用服务端 util.hpp 代码, 实现相同的功能.
客户端网络通信模块:
通过不断遍历备份文件夹中的文件, 判断其是否需要上传, 判断条件: 1. 该文件没有存储文件信息, 则说明该文件是刚放入备份文件夹的, 还没有备份上传, 需要备份. 2. 若存在备份文件信息, 则比较当前的etag与之前保存的etag比较, 若不相同则需要上传( 若最后一次访问时间与当前时间比较, 大于指定时间则认为用户已对文件修改完成, 可以上传, 否则认为用户还在修改, 先不上传, 等下次循环再判断时间是否大于, 大于则认为修改完成, 可以上传.)
需要上传则使用 httplib 搭建客户端, 通过对库中MultipartFormData 设置区域标识字段 name="file" , 文件名filename="xxx" , File_util中 Read(xxx) 读取该文件的内容到content正文中, 再将MultipartFormData结构体通过 httplib::Post()接口即可实现上传.
项目的不足及拓展思路:
1. 上传时也可以实现断点续传功能 , 内存中的管理的数据也可以采用热点管理, 也可以自己实现http通信锻炼能力.
2. 由于没有实现用户管理, 每个用户都是一个未知客户端, 每个客户端都是共享的上传下载 , 所以目前的项目对同名文件是只做了简单的覆盖处理, 后续改进可以考虑同一个客户端的二次备份, 不同客户端的同名文件的不同处理.
3. 可以使用单例模式管理文件信息,能够让配置信息的管理控制更加统一灵活。且有了互斥锁对信息的保护才安全.
4. 给客户端开发一个好看的界面( 需要学习前端知识 ),让监控目录可以选择.
3. 文件压缩模块也可以使用线程池实现