【项目】云备份系统笔记

服务端:包含多个文件,每个文件代表不同的模块和功能

cloud.conf:配置文件包含

热点文件的时间(30,超过30s不再访问,就不再是热点文件)

服务器的ip和端口(绑定监听)

下载请求的前缀("/download/")

压缩文件的后缀(.lz)

压缩文件的路径("./packdir/",不再是热点文件会主动进行压缩存储)

备份文件的路径("./backdir/",用户端传过来需要保存的文件都在这个文件夹中,超过一定时间不被访问,就会被主动压缩,并且将压缩文件方法哦packdir中)

上传文件的备份信息文件("./cloud.dat" ,存放了已经被备份下来文件的基本信息,文件名)

config.hpp:

就包含一个类,config,是读取配置文件的类,因为配置文件只有一份,所以可以采用单例模式,把config类的构造函数私有化,只能通过一个公共成员函数getinstance()来创建对象(new config()),在类的内部可以调用私有化构造函数,构造函数里面再调用读取配置文件的函数,用自己实现的工具类读取配置文件,将读到的内容放到一个字符串中,然后再将字符串用Json进行序列化,然后给类中的成员变量进行赋值,类中的成员变量就包含了cloud.conf里面包含的信息,一一对应。然后实现了获取每个成员变量的成员函数。

注意:

static Config *GetInstance()
        {
            if (_instance == nullptr)
            {
                _mutex.lock();
                if (_instance == nullptr)
                {
                    _instance = new Config();
                }
                _mutex.unlock();
            }

            return _instance;
        }
  1. 限制实例化:单例模式将类的构造函数设为私有,以防止外部直接创建对象。
  2. 提供访问点:通过一个静态方法(通常是 getInstance)来提供对该唯一实例的访问。
  3. 确保唯一性:使用静态成员变量来存储类的唯一实例,以确保只有一个实例被创建。

所以_instance必须是static修饰,属于整个类且唯一,同样的锁也是一样,只要需要创建这个类的实例,那么多个线程之间用到的是同一把锁。或者说使_mutex成为类级别的资源。

所以这两个成员变量都要在类外面进行初始化,对于_instance可以初始化为nullptr,但是对于锁不需要,因为std::mutex是一个不需要显示初始化的类,在创建时自动处于未锁定状态。所以初始化代码为:

std::mutex Config::_mutex;

为什么进行两次判断是否为nullptr?

第一次:在多线程并发创建实例的情况下,如果其他线程已经创建了,那么该线程可以直接获取实例,不用再次创建。

加锁:如果发现没有实例,就要创建实例,但创建实例的过程只能由一个线程来完成,保证单例模式的原则(不然会有多个线程new config()),因为配置文件资源是唯一的,对于临界区资源一般都要进行加锁保护(防止数据竞争,线程间对_instance的写操作会干扰,出现未定义行为)。

第二次:防止其他线程已经创建实例后,再次创建实例,因为当多个线程同时进入都判断没有实例,然后都会去竞争锁,如果一个线程竞争到锁,把实例创建之后释放锁,其他线程拿到锁,没发现实例已经创建,就会重复创建,所以必须要进行二次检查。

data.hpp:主要是管理备份文件的基本信息,包含数据管理模块(class DataManager)和文件备份信息结构体(BackupInfo)

BackupInfo:包含以下成员变量和通过绝对路径生成backupinfo的构造函数

        public:
            bool _pack_flag;  //是否被压缩
            size_t _fsize;    //文件大小
            time_t _mtime;    //最近修改时间
            time_t _atime;    //最近访问时间
            string _real_path;//绝对路径
            string _pack_path;//压缩存放的路径
            string _url;      //请求路径

DataManager:

 DataManager ()
            {    
                pthread_rwlock_init(&_rwlock,nullptr);               
                _backup_file=Config::GetInstance()->GetBackUpFile();  
                InitLoad();   
            };

构造函数包括:初始化读写锁、初始化基本备份信息文件名、

数据管理模块的初始化:读取基本备份信息文件(如果不存在文件就创建)到字符串,对字符串进行反序列化生成多个backupinfo放入到hash表中进行管理,接下来对基本信息的管理变成了对hash表的管理(包括增删查改的操作),从hash表中读取数据要加读锁,对hash进行写操作要加写锁。

  • 读锁 (pthread_rwlock_rdlock):允许多个线程同时持有读锁,适用于并发读取数据的场景。其他线程不能获取写锁,确保数据在读操作期间不会被修改。

  • 写锁 (pthread_rwlock_wrlock):只能有一个线程持有写锁,其他线程无法持有读锁或写锁。这种锁定机制用于数据修改时,确保没有其他线程在修改期间读取或修改数据。

最后还包括了数据的持久化,即把hash表中backinfo同步到基本备份信息文件当中,当hash表中的数据内容发生变化,就要把数据变化也同步到基本备份信息文件。

hot.hpp:包含一个热点管理的类,class HotManager

private:
        string _back_dir;  //备份文件的存放目录
        string _pack_dir;  //备份文件压缩之后的存放目录
        string _pack_suffix;    //压缩文件的后缀
        int _hot_time;          //超过多少时间没有访问就是非热点文件要进行压缩

该类的构造函数中,也是获取了配置文件的单例,然后从配置文件中获取备份文件的目录和压缩之后的存放目录、热点时间和压缩文件的后缀,同时创建这两个目录。

在RunModule()函数中,是一个死循环,会不断去检测备份文件目录中的文件是否是热点文件,即是否超过_hot_time没有被访问了,如果发现不是热点文件了,或者发现备份文件存在,但是内存数据管理并不存在这个文件的基本信息backinfo(其实就是新上传上来的文件),但不管存不存在文件的基本信息,文件存在且访问时间超过了热点时间,那么就要进行压缩,压缩存放的路径可以通过backinfo中的_pack_path获取,同时对backinfo里面的是否压缩字段进行更新,放入hash表中进行管理。同时每次检测可以休眠一秒,因为如果是空目录的话,每次遍历检查都发现没有文件,那么就会消耗cpu资源。

service.hpp:

绑定了请求路径对应的请求方法,如文件的上传、下载和备份文件基本信息的展示。

下载文件:分为正常下载和断点续传,要让http支持断点续传的功能,那么服务端返回给客户端的响应里面就要包含ETag:自定义请求头(文件名-文件最后一次修改时间-文件大小)和Accept-Ranges:bytes请求头,客户端接受到后,就会把ETag的值保存下来,下次再想服务端发送请求时就会携带上If-Range请求头里面的值就是服务端传给客户端的Etag,但如果没有这个请求头就代表正常下载的请求,不会用到断点续传,如果存在If-Range取到对应的ETag值,还要通过url获取hash表中backinfo中的文件信息来判断该文件最近是否被修改过,如果被修改了,还是重新下载,如果没被修改,才是断点续传。

断点续传:客户端发送请求时会携带Range请求头:start-end,代表请求的数据范围,服务端解析出start和end,然后读取文件中对应范围的数据写入到响应的正文(response的body)当中,然后带上响应头Content-Range:bytes start-end/fsize,同时设置响应状态码为206,代表服务器成功处理了部分 GET 请求。

常用状态码:

1xx:继续,代表客户端应该继续请求

200:ok,请求成功

204:No Content,没有内容,服务器成功处理,但没有返回内容。

206:Partial Content,服务器成功处理了部分get请求

301:永久重定向,请求的资源被永久移动到了新的URL。

302:临时重定向,资源只是临时被移动

304:未修改,请求的资源没有修改,服务器不会返回任何数据

400:客户端请求的语法错误,服务器无法理解

401:要求用户的身份认证

403:禁止,服务器理解客户端的请求,但拒绝执行请求

404:服务器没有找到资源,也可能是服务器拒绝请求

500:服务器内部错误,无法完成请求

501:服务器不具备完成请求的功能

客户端:

cloud.hpp:包含一个backup类,里面实现了备份的功能,也就是备份模块。

bool RunModule()
		{
			while (true)
			{
				FileUtil fu(_back_dir.c_str());
				vector<string> arry;
				fu.ScanDirectory(&arry);
				for (auto& file : arry)
				{
					if (IsNeedUpload(file))
					{
						if (Upload(file) == true)
						{
							_data->Insert(file, GetFileId(file));
						}
					}
				}
				Sleep(1);
			}
		}

客户端生成一个备份文件夹,只要是放入这个文件夹中的文件,会被备份模块进行管理,该模块会去遍历这个文件夹,把遍历到的文件名放到一个容器中,再对容器进行遍历,也就是对指定文件夹下的所有文件进行遍历,看是否需要上传文件到服务器进行备份,判断的逻辑为:距离上一次修改文件的时间是否已经过去了三秒,如果超过了3秒就把文件进行上传,但存在两种情况,一种情况是文件是新添加进来的需要进行上传备份,内存中不存在这个文件的备份信息,上传文件之后会将备份信息加载到内存用hash表进行管理,一种情况是文件已经被上传过了,内存中已经存在这个文件的备份信息,但是这个文件期间被修改过了,也需要重新上传,所以可以通过备份信息里面的文件标识来判断文件是否被修改过,因为文件标识中包含了文件最后一次修改的时间,如果文件被修改了,那么文件标识就会不同,可以重新上传。

data.hpp:包含一个DataManager的类,进行数据管理,是数据管理模块。

string _back_file;
unordered_map<string, string> _table;

 备份文件名和进行备份信息管理的hash表

InitLoad():从备份信息文件中读取备份信息,信息是以key val\n构成的字符串,key是文件名,val是文件标识(文件名+文件大小+最后一次修改时间),进行反序列化之后存入hash表中,同样以文件名作为key,文件标识作为val,完成数据管理的初始化。

Storage():把hash表中数据持久化(要进行自定义序列化)到备份信息文件当中,每次对hash进行了更新或者新增的操作都要更新备份信息文件。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值