【云备份项目两万字总结】服务端篇 -----附源码

news2024/11/18 15:37:37

在这里插入图片描述

项目总结

  • 整体回顾
  • 逐步实现
    • utill.hpp
    • config.hpp
    • data.hpp
    • hot.hpp
    • service.hpp
  • 代码

整体回顾

服务端的目标是:

  1. 对客户端的请求进行处理
  2. 管理客户端上传的文件

于客户端进行数据交换,我们需要引入网络,所以我们引入第三方库----httplib.h库,搭建服务端。在进行网络通讯时,数据需要被系列化和反序列化,否则有数据丢失的风险,还需引入json
在管理文件时,需要进行热点管理,把非热点文件进行压缩也需要引入第三方库 ----bundle.h,对文件进行压缩。

因此我们可以给服务端的各种功能实现划分模块,逐步实现服务端的整体功能。

要对上传的文件的文件进行管理,我们需要---- 文件管理模块
同时,在将文件管理后,我们需要对其进行更一步的热带管理模块,热点管理的实现是在文件管理模块的实现之上的。
在本地测试好了上述两个模块后,我们可以进行网络通讯了。
需要一个网络通讯模块,通过其搭建我们的服务端,
能进行网络通讯后,还需要一个业务处理模块,处理客户端发送过来的请求,并予以响应。

逐步实现

先创建一个库,util.hpp工具库文件
里面有我们自己实现的几个util工具类。

工欲善其事,必先利其器。

utill.hpp

class FileUtil{}
创建一个FileUtil 类,文件工具类,对系统的文件接口进行封装,便于我们对文件快捷操作,提供对文件的增删查改

class FileUtil{
private:
   std::string _name;
public:
  FileUtil(const std::string &name);
  size_t FileSize();// 文件大小
  time_t LastATime(); // 最后一次查看文件时间
  time_t LastMTime(); // 最后一次修改文件的时间
  std::string FileName();   //文件名字
  bool GetPosLen(std::string *content, size_t pos, size_t len);  //获取文件流pos 后len个长度的数据
  bool GetContent(std::string *content);  //获取文件内容
  bool SetContent(std::strint *content);   //写入文件
  bool Compress(const std::string &packname);   //压缩文件
  bool UnCompress(const std::string &filename);  //解压文件
  bool Exists();     //判断文件是否存在
  bool CreateDirectory();    //创建一个目录
  bool ScanDirectory(std::vector<std::string> *arry);  //查看目录下的文件内容

}
  1. FileUtile 工具提供了对文件的增删查改的功能,
  2. 也提供了对目录的查看功能,和创建目录的功能

其中,对文件压缩解压缩时,我们需要借用bundle.h库的函数,如何使用bundle库里的函数,在GitHub上有完整的教程。
在这里插入图片描述

同时,我们在查看目录时,需要借助filesystem库的使用,但是只有在c++17以上的版本才支持filesystem

在这里插入图片描述

注意:

  • 在Windows下,我们要选择了vs2017以上的版本
  • 在Linux下,我们需要将gcc进行升级,7.3版本

class jsonutil

jsonutil类为网络通讯时的数据提供系列化和反序列化的功能,当然需要引入json库

在这里插入图片描述

至此,我们的基础工具已经完善,可以以此为基础,更一步完善服务端的功能。

config.hpp

项目配置信息的管理,启动项目时,会自动从 .conf文件加载项目的配置信息。需要修改部分内容时,不需要在代码上修改,只需要修改配置文件,然后重启服务器即可。

采用json 格式将配置信息存放在Cloud.conf中,当启动服务器时,由服务器从.conf文件中读取关键数据。

Cloud.conf 文件

{
“hot_time” : 30,
“server_port” : 9090,
“server_ip” : “1.1.1.1”,
“url_prefix” : “/download/”,
“arc_suffix” : “.lz”,
“pack_dir” : “./packdir/”,
“back_dir” : “./backdir/”,
“manager_file” : “./back.dat”
}

#define CONFIG_FILE "./cloud.conf"
class Config{
private:
	time_t _hot_time;
	int _server_port;
	std::string _server_ip;
	std::string _url_prefix;
	std::string _arc_suffix;
	std::string _pack_dir;
	std::string _back_dir;
	std::string _manager_file;//备份信息的配置管理
private:
	static std::mutex _mutex;
	static Config *_instance;
	Config();
public:
	bool ReadConfig(const std::string &filename);
	int GetHotTime();
	int GetServerPort();
	std::string GetServerIp();
	std::string GetURLPrefix();
	std::string GetArcSuffix();
	std::string GetPackDir();
	std::string GetBackDir();
	std::string GetManagerFile();
public:
   static Config *GetInstance();
};


且,在实现配置信息类时,我们采取单例模式。

data.hpp

data.hpp是数据管理模块的主要部分。

要管理文件数据,就得先对文件的信息进行组织。
struct BackupInfo{}

typedef struct BackupInfo
  {
    bool pack_flag; // 文件是否被压缩的标识
    time_t atime;   // 最后一次查看时间
    time_t mtime;   // 最后一次修改时间
    size_t fsize;    //文件大小
    std::string real_path; // 文件在服务器上的真实存储路径
    std::string url;       // 客户端访问文件时的请求url
    std::string packpath;  // 压缩包存储路径

    bool FillBackupInfo(const std::string &realpath){}
   }BackupInfo;

有了这些数据后,我们能准确的描述一个文件,并可以很好的进行管理。

上传的文件信息都以BackuoInfo的模式,以json的格式存储在backup_file中,当程序启动时,需要去文件加载数据到内存。同时,在新上传文件后,我们也需要将文件数据永久化存储到backup_file中。也需要支持对已经被管理的文件信息的增删查改(我们暂时不支持对信息的删除,现在只涉及最基础的功能实现,更多功能在已经构建好整个框架后会进一步实现)。

整个数据管理模块,也为让上层迅速查找文件的备份信息

class DateManager{}

class DataManager{
private:
	FileUtil _backup_file;
	pthread_rwlock_t _rwlock;   //  读写锁
	std::unordered_map<std::string, BackupInfo> _table;
public:
	DataManager();
	bool InitLoad();//初始化程序运行时从文件读取数据
	bool Storage();    //每次有信息改变则需要持久化存储一次
	bool Insert(const std::string &key, const BackupInfo &val);
	bool Update(const std::string &key, const BackupInfo &val);
	bool GetOneByURL(const std::string &key, BackupInfo *info);
	bool GetOneByRealPath(const std::string &key, BackupInfo *info);
	bool GetAll(std::vector<BackupInfo> *arry);
};

其具体实现内容在项目日志时已经说过,在此不再重复。

注意:
我们是对 _table 加上了rwlock 读写锁,因为这里的并发访问场景更多的是读读,读写场景,能提高服务器运行速度。
同时,加锁的原因是:
httplib库中,使用了线程池的技术,当服务端accept一个客户端后,会另起一个线程在服务端处理请求,所以, _table属于临界资源,需要加锁保护。

我们的Storage()覆盖式存储,是将 内存中 _table里的所有数据进行反序列,将 backup_file里的内容进行覆盖。

hot.hpp

热点管理模块就压要简单一点,很大一部分工作在数据管理模块已经完成。

循环遍历目录下的所有文件,然后通过文件最后一次修改时间来判断该文件是否为热点文件,然后压缩至指定目录即可。

extern cloud::DataManager *_data;
class HotManager{
private:
	std::string _back_dir;
	std::string _pack_dir;
	std::string _pack_suffix;
	time_t _hot_time;
public:
HotManager();
	bool HotJudge(const std::string &file);
	bool RunModule();
};

因为数据管理是要在多个模块中访问的,因此将其作为全局数据定义。

service.hpp

云备份项目中 ,业务处理模块是针对客户端的业务请求进行处理,并最终给与响应。而整个过程中包含以下要实现
的功能:

  1. 借助网络通信模块httplib库搭建http服务器与客户端进行网络通信
  2. 针对收到的请求进行对应的业务处理并进行响应(文件上传,列表查看,文件下载(包含断点续传))

响应给客户端的 rsp在之前的项目日志里也有描述。

class Service{
private:
	int _server_port;
	std::string _server_ip;
	std::string _url_prefix;
	httplib::Server _srv;
private:
	static void Upload(const httplib::Request &req, httplib::Response &rsp);
	static void List(const httplib::Request &req, httplib::Response &rsp);
	static void Download(const httplib::Request &req,httplib::Response &rsp);
public:
	Service();
	bool RunModule();
}

注意:
业务处理的回调函数没有传入参数的地方(大概是因为,回调函数的模板被固定化了),因此无法直接访问外部的数据管理模块数据,因此将数据管理模块的对象定义为全局数据,在这里声明一下,就可以在任意位置访问了,且回调函数必须为静态函数,(类内函数成员变量会隐藏一个this指针)。

文件上传函数和文件列表查看函数都按照思路来写。

文件下载函数有部分事项需要注意:

1 . 服务端要判断是否需要进行断点重传,判断条件:
有If-Range字段且,这个字段的值与请求文件的最新etag一致则符合断点续传
也就是说需要在客户端请求里有If-Range字段,且在这段时间内,文件的数据内容没有被修改过。
这是Download 函数正常的响应rsp:

HTTP/1.1 200 OK
Content-Length: 100000
ETags: "filename-size-mtime一个能够唯一标识文件的数据"
Accept-Ranges: bytes

Accept-Ranges报头

服务器使用 HTTP 响应头 Accept-Ranges 标识自身支持范围请求 (partial
requests)。字段的具体值用于定义范围请求的单位。
当浏览器发现Accept-Ranges头时,可以尝试继续中断了的下载,而不是重新开始。

这是Download执行断点续传的rsp:

HTTP/1.1 206 Partial Content
Content-Length:
Content-Range: bytes 89-999/100000
Content-Type: application/octet-stream
ETag: "inode-size-mtime一个能够唯一标识文件的数据"
Accept-Ranges: bytes

httplib内部实现了对于区间请求也就是断点续传请求的处理
只需要我们用户将文件所有数据读取到rsp.body中,它内部会自动根据请求
区间,从body中取出指定区间数据进行响应
,并且会自动填充rsp内容。

代码

代码里边会有博主的一些思考和理解,各位大佬见笑了😅

util.hpp

#pragma once

#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <memory>
#include <experimental/filesystem>
#include "bundle.h"
#include <jsoncpp/json/json.h>

namespace Cloud
{
	namespace fs = std::experimental::filesystem;
	class FileUtil
	{
	private:
		std::string _Filename;

	public:
		FileUtil(std::string fname) : _Filename(fname)
		{
		}

		int64_t Filesize() // 提取文件大小
		{
			struct stat st;
			if (stat(_Filename.c_str(), &st) < 0)
			{
				std::cerr << "get Filesize fail"
						  << std::endl;
				return -1;
			}
			return st.st_size;
		}

		std::string Filename() // 提取文件名
		{
			// /a/b/文件名

			size_t pos = _Filename.find_last_of("/");
			if (pos == std::string::npos)
			{
				return _Filename;
			}
			return _Filename.substr(pos + 1);
		}

		time_t LastMtime() // 提取文件最后一次的修改时间(文件内容)
		{
			struct stat st;
			if (stat(_Filename.c_str(), &st) < 0)
			{
				std::cerr << "get File LastMtime fail\n"
						  << std::endl;
				return -1;
			}
			return st.st_mtime;
		}

		time_t LastAtime() // 提取文件最后一次的访问时间
		{
			struct stat st;
			if (stat(_Filename.c_str(), &st) < 0)
			{
				std::cerr << "get File LastAtime fail\n"
						  << std::endl;
				return -1;
			}
			return st.st_atime;
		}

		time_t LastCtime() // 提取文件最后一次的修改时间(文件内容 || 文件属性)
		{
			struct stat st;
			if (stat(_Filename.c_str(), &st) < 0)
			{
				std::cerr << "get File LastCtime fail\n"
						  << std::endl;
				return -1;
			}
			return st.st_ctime;
		}

		bool Remove()
		{
			remove(_Filename.c_str());
			return true;
		}

		bool GetPosLen(std::string &body, size_t pos, size_t len)
		{
			size_t fsize = this->Filesize();
			if (pos + len > fsize)
			{
				std::cout << "get file len is error\n";
				return false;
			}
			std::ifstream ifs;
			ifs.open(_Filename, std::ios::binary);
			if (ifs.is_open() == false)
			{
				std::cout << "read open file failed!\n";
				return false;
			}
			ifs.seekg(pos, std::ios::beg);
			body.resize(len);
			ifs.read(&body[0], len);
			if (ifs.good() == false)
			{
				std::cout << "get file content failed\n";
				ifs.close();
				return false;
			}
			ifs.close();
			return true;
		}

		bool GetContent(std::string &body)
		{
			size_t fsize = this->Filesize();
			return GetPosLen(body, 0, fsize);
		}

		bool SetContent(const std::string &body)
		{
			std::ofstream ofs;
			ofs.open(_Filename, std::ios::binary);
			if (ofs.is_open() == false)
			{
				std::cout << "write open file failed!\n";
				return false;
			}
			ofs.write(&body[0], body.size());
			if (ofs.good() == false)
			{
				std::cout << "write file content failed!\n";
				ofs.close();
				return false;
			}
			ofs.close();
			return true;
		}

		bool Compress(const std::string &packname)
		{
			// 1. 获取源文件数据
			std::string body;
			if (this->GetContent(body) == false)
			{
				std::cout << "compress get file content failed!\n";
				return false;
			}
			// 2. 对数据进行压缩
			std::string packed = bundle::pack(bundle::LZIP, body);
			// 3. 将压缩的数据存储到压缩包文件中
			FileUtil fu(packname);
			if (fu.SetContent(packed) == false)
			{
				std::cout << "compress write packed data failed!\n";
				return false;
			}
			return true;
		}

		bool UnCompress(const std::string &filename)
		{
			// 将当前压缩包数据读取出来
			std::string body;
			if (this->GetContent(body) == false)
			{
				std::cout << "uncompress get file content failed!\n";
				return false;
			}
			// 对压缩的数据进行解压缩
			std::string unpacked = bundle::unpack(body);
			// 将解压缩的数据写入到新文件
			FileUtil fu(filename);
			if (fu.SetContent(unpacked) == false)
			{
				std::cout << "uncompress write packed data failed!\n";
				return false;
			}
			return true;
		}

		
		bool Exists()
		{
			return fs::exists(_Filename);
		}

		bool CreateDirectory()
		{
			if (this->Exists())
				return true;
			return fs::create_directories(_Filename);
		}

		bool ScanDirectory(std::vector<std::string> &arry)
		{
			CreateDirectory();
			for (auto &p : fs::directory_iterator(_Filename))
			{
				if (fs::is_directory(p) == true)
				{
					continue;
				}
				// relative_path 带有路径的文件名
				arry.push_back(fs::path(p).relative_path().string());
			}
			return true;
		}
	};

	class jsonutil
	{
	public:
		static bool Serialize(const Json::Value &root, std::string &str) // 序列化
		{
			Json::StreamWriterBuilder swb;
			std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
			std::stringstream ss;
			if (sw->write(root, &ss) != 0)
			{
				std::cout << "json write failed!\n";
				return false;
			}
			str = ss.str();
			return true;
		}

		static bool UnSerialize(const std::string &str, Json::Value &root) // 反序列化
		{
			Json::CharReaderBuilder crb;
			std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
			std::string err;
			bool ret = cr->parse(str.c_str(), str.c_str() + str.size(), &root, &err);
			if (ret == false)
			{
				std::cout << "parse error: " << err << std::endl;
				return false;
			}
			return true;
		}
	};
}

data.hpp

#pragma once

#include <unordered_map>
#include <pthread.h>
#include <iostream>
#include "util.hpp"
#include "config.hpp"
#include <string>
#include <stdio.h>

// 服务端要管理文件数据,需要  先描述,在组织, 构建一个文件属性结构体,通过这个结构体来管理所有的文件
namespace Cloud
{

  typedef struct BackupInfo
  {
    bool pack_flag; // 文件是否被压缩的标识
    time_t atime;   // 最后一次查看时间
    time_t mtime;   // 最后一次修改时间
    size_t fsize;
    std::string real_path; // 文件在服务器上的真实存储路径
    std::string url;       // 客户端访问文件时的请求url
    std::string packpath;  // 压缩包存储路径

    bool FillBackupInfo(const std::string &realpath)
    {
      FileUtil ft(realpath);
      if (ft.Exists() == false)
      {
        std::cerr << "fill backupinfo:file not exists" << std::endl;
        return false;
      }
      Config *cf = Config::Getinstance();
      pack_flag = false;
      atime = ft.LastAtime();
      mtime = ft.LastMtime();
      fsize = ft.Filesize();
      real_path = realpath;
      // ./backdir/a.txt   ->	  /download/a.txt
      url = cf->GetDownloadPrefix() + ft.Filename();
      // ./packdir/a.txt   ->   ./packdir/a.txt.lz
      packpath = cf->GetPackDir() + ft.Filename() + cf->GetPackFileSuffix();

      return true;
    }

  } BackupInfo;

  class DateManager
  {

  private:
    std::string _backup_file;                           // 文件的信息都会以json的格式存放在 一个backup文件里
    pthread_rwlock_t _rwlock;                           // 对backup文件会存在并发访问的问题     ------->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>   ????????????????? 不懂为什么有锁
    std::unordered_map<std::string, BackupInfo> _table; //  以hash结构,url 为key ,BackupInfo 为val ,查找迅速

  public:
    // 从文件中读取数据进行初始化    对文件增删查改   对文件的永久化储存
    DateManager()
    {
      // printf("准备Datemanager初始化\n");
      _backup_file = Config::Getinstance()->GetBackupFile();
      pthread_rwlock_init(&_rwlock, nullptr);
      initload();
      // std::cout<<"初始化成功"<<std::endl;
    }

    bool insert(const BackupInfo &bf)
    {
      //  ?  我自己的思路   --------- 传入一个 filename ,然后在insert函数中,自己填充BackupInfo数据 ,在插入进_table中  ----》 可以减少上层的工作量 (我觉得)
      pthread_rwlock_wrlock(&_rwlock);
      std::string url = bf.url;
      _table[url] = bf;
      pthread_rwlock_unlock(&_rwlock);
      Storage();
      return true;
    }

    bool update(const BackupInfo &bf)
    {
      //   ?  问题同 insert函数
      pthread_rwlock_wrlock(&_rwlock);
      std::string url = bf.url;
      _table[url] = bf;
      pthread_rwlock_unlock(&_rwlock);
      Storage();
      return true;
    }

    bool GetOneByURL(const std::string &url, BackupInfo *info)
    {
      pthread_rwlock_rdlock(&_rwlock);
      auto it = _table.find(url);
      if (it != _table.end())
      {
        *info = it->second;
      }
      else
      {
        pthread_rwlock_unlock(&_rwlock);
        return false;
      }
      pthread_rwlock_unlock(&_rwlock);

      return true;
    }

    bool GetOneByRealpath(const std::string &realpath, BackupInfo *info)
    {
      // std::cout<<"准备拿锁"<<std::endl;
      pthread_rwlock_rdlock(&_rwlock);
      // std::cout<<"拿到锁了"<<std::endl;
      std::unordered_map<std::string, BackupInfo>::iterator it = _table.begin();

      // std::cout<<"找到初始位置了"<<std::endl;
      for (; it != _table.end(); ++it)
      {
        if (it->second.real_path == realpath)
        {
          *info = it->second;
          pthread_rwlock_unlock(&_rwlock);
          return true;
        }
      }
      pthread_rwlock_unlock(&_rwlock);
      return false;
    }

    void GetAll(std::vector<BackupInfo> *arry)
    {
      pthread_rwlock_wrlock(&_rwlock);
      for (auto it = _table.begin(); it != _table.end(); ++it)
      {
        arry->push_back(it->second);
      }
      pthread_rwlock_unlock(&_rwlock);
    }

    bool Storage() // 每次有信息改变则需要持久化存储一次
    {
      // 1. 获取所有数据
      std::vector<BackupInfo> arry;
      this->GetAll(&arry);
      // 2. 添加到Json::Value
      Json::Value root;
      for (int i = 0; i < arry.size(); i++)
      {
        Json::Value item;
        item["pack_flag"] = arry[i].pack_flag;
        item["fsize"] = (Json::Int64)arry[i].fsize;
        item["atime"] = (Json::Int64)arry[i].atime;
        item["mtime"] = (Json::Int64)arry[i].mtime;
        item["real_path"] = arry[i].real_path;
        item["packpath"] = arry[i].packpath;
        item["url"] = arry[i].url;
        root.append(item); // 添加数组元素
      }
      // 3. 对Json::Value序列化
      std::string body;
      jsonutil::Serialize(root, body);
      // 4. 写文件
      FileUtil fu(_backup_file);
      fu.SetContent(body);
      return true;
    }

    bool initload() 初始化程序运行时从文件读取数据      ------->  为什么不从备份目录中提取数据?  --- 备份目录下的文件会被压缩至压缩目录
    {
      // 1. 从文件中读取数据
      // printf("准备读数据\n");
      FileUtil ft(_backup_file);
      // printf("读数据成功\n");
      if (ft.Exists() == false) // 如果文件不存在,说明还没有数据存入数据文件,也就是还没有创建数据文件
      {
        //  printf("文件不存在\n");
        return true;
      }
      std::string str;
      // printf("准备获得文\n");
      ft.GetContent(str);
      //  printf("获得文成功\n");
      // 2. 将数据反序列化
      //  printf("准备序列化\n");
      Json::Value root;
      jsonutil::UnSerialize(str, root);
      // printf("反序列化成功\n");
      // 3. 将数据插入_table
      // printf("准备插入数据:%d\n",root.size());
      for (int i = 0; i < root.size(); i++)
      {
        // std::cout<<"开始插入数据"<<std::endl;
        BackupInfo info;
        info.pack_flag = root[i]["pack_flag"].asBool();
        info.fsize = root[i]["fsize"].asInt64();
        info.atime = root[i]["atime"].asInt64();
        info.mtime = root[i]["mtime"].asInt64();
        info.packpath = root[i]["packpath"].asString();
        info.real_path = root[i]["real_path"].asString();
        info.url = root[i]["url"].asString();
        // std::cout<<"插入:"<<info.url<<std::endl;
        insert(info);
      }
      return true;
    }

    ~DateManager()
    {
      pthread_rwlock_destroy(&_rwlock);
    }
  };

}

hot.hpp

#pragma once

#include <unistd.h>
#include "data.hpp"
#include <iostream>

extern Cloud::DateManager *_data;

namespace Cloud
{
  class HotManager
  {
  private:
    std::string _back_dir;
    std::string _pack_dir;
    std::string _pack_suffix;
    int _hot_time;

  public:
    HotManager()
    {
      Config *cng = Config::Getinstance();
      _back_dir = cng->GetBackDir();
      _pack_dir = cng->GetPackDir();
      _pack_suffix = cng->GetPackFileSuffix();
      _hot_time = cng->GetHotTime();
      FileUtil tmp1(_back_dir);
      FileUtil tmp2(_pack_dir);
      tmp1.CreateDirectory();
      tmp2.CreateDirectory();
    }

    bool HotJudge(const std::string &filename) // 返回true 说明为非热点文件
    {
      FileUtil fu(filename);
      time_t curtime = time(NULL);
      if (curtime - fu.LastAtime() > _hot_time)
        return true;
      return false;
    }

    void RunModel() // 不断循环检测 back_dir 目录下的文件 ,进行热点管理
    {
      while (true)
      {
        // 1. 遍历备份目录,获取所有文件名
        FileUtil fu(_back_dir);
        std::vector<std::string> arry;
        fu.ScanDirectory(arry);
        // std::cout<<"准备判断是否为热点文件"<<std::endl;//,没什么问题
        //  2. 判断文件是否为热点文件
        // std::cout<<arry.size()<<std::endl;
        for (const auto &it : arry)
        {
          // std::cout<<"开始遍历判断是否为热点文件"<<std::endl;
          // std::cout<<it<<std::endl;
          if (HotJudge(it) == false)
          {
            // std::cout<<"不是热点文件"<<std::endl;
            continue;
          }

          // 获取文件的备份信息
          BackupInfo info;

          //  std::cout<<"准备执行HOT里的GetonebyRealpath"<<std::endl;
          if (_data->GetOneByRealpath(it, &info) == false)
          {
            //     std::cout<<"准备执行fillBackupInfo"<<std::endl;
            info.FillBackupInfo(it);
          }

          // 3. 对非热点文件进行压缩

          FileUtil tmp(it);
          tmp.Compress(info.packpath);
          // 4. 删除源文件,修改备份信息
          tmp.Remove();
          info.pack_flag = true;
          _data->update(info);
        }
        // std::cout<<"准备进入睡眠"<<std::endl;
        usleep(1000);
      }
    }
  };
}

service.hpp

#pragma once

#include <errno.h>
#include <string>
#include "data.hpp"
#include "hot.hpp"
#include "httplib.h"

// 服务端构建服务器, 为客户端提供  上传文件(upload)   下载文件(get)         文件列表查看()三个req
// 并对客户端 进行响应              响应上传成功         响应下载的文件数据    响应一个展示文件备份列表的前端页面

extern Cloud::DateManager *_data;

namespace Cloud
{
  std::string totimestring(const time_t &tm)
  {
    struct tm *tmp = localtime(&tm);
    char buffer[1024];
    snprintf(buffer, sizeof(buffer), "%d-%d-%d %d:%d:%d", tmp->tm_year + 1900, tmp->tm_mon + 1, tmp->tm_yday,
             tmp->tm_hour, tmp->tm_min, tmp->tm_sec);
    return buffer;
  }

  class Service
  {
  private:
    std::string _server_ip;
    uint16_t _server_port;
    std::string _download_prefix; // 我自己粗略认为这是充当客户端请求下载文件时url中的前一部分
    httplib::Server _server;

  public:
    Service()
    {
      printf("Service 开始初始化\n");
      Config *config = Config::Getinstance();
      _server_ip = config->GetServerIp();
      _server_port = config->GetServerPort();
      _download_prefix = config->GetDownloadPrefix();
      printf("ser 初始化成功\n");
    }
    void RunModule()
    {
      printf("Server RunModulem 开始\n");
      _server.Post("/Upload", Upload);
      _server.Get("/", Showlist);
      _server.Get("/Showlist", Showlist);

      // 下载文件需要匹配具体的文件名,需要借用正则表达式
      _server.Get(_download_prefix + ".*", Download);
      printf("server 开始listen\n");
      std::cout << _server_ip << "::" << _server_port << std::endl;
      if (_server.listen(_server_ip.c_str(), _server_port) == false)
      {
        std::cout << "listen error" << errno << std::strerror(errno) << std::endl;
      }
    }

  private:
    static void Upload(const httplib::Request &req, httplib::Response &rsp) // 上传文件数据
    {
      // 1. 对req进行反序列化(httplib已经帮我们做过了) ,提取数据
      printf("收到一个upload请求\n");
      auto ret = req.has_file("file"); // 判断req请求中是否包含 上传的文件字段
      if (ret == false)
      {
        rsp.status = 400;
        return;
      }
      // 2. 拿到文件名,拿到文件数据
      const auto &file = req.get_file_value("file");
      std::string filename = file.filename;
      std::string filecontent = file.content;
      // 3. 将其保存至 backdir目录下 ,
      std::string backdir = Config::Getinstance()->GetBackDir();

      std::cout << "backfilename:" << backdir + filename << std::endl;
      FileUtil fu(backdir + filename);
      fu.SetContent(filecontent);
      // 修该组织文件备份的管理信息
      BackupInfo info;
      info.FillBackupInfo(backdir + filename);
      _data->insert(info);
      // 4. 同时填充rsp
      rsp.status = 200;
      printf("upload 完成\n");
      return;
    }
    // 唯一标识符 filename-filesize-lastmtime
    static std::string GetETag(const BackupInfo &info)
    {
      FileUtil fu(info.real_path);
      std::string etag = fu.Filename();
      etag += "-";
      etag += std::to_string(info.fsize);
      etag += "-";
      etag += std::to_string(info.mtime);
      return etag;
    }

    // static void Download(const httplib::Request &req, httplib::Response &rsp)
    // {
    //   printf("收到一个Download请求\n");
    //   // 1. 从req中提取url,通过url找到 获取文件备份信息
    //   BackupInfo info;
    //   _data->GetOneByURL(req.path, &info);
    //   std::cout << req.path << std::endl;
    //   std::cout << info.packpath << ":" << info.real_path << std::endl;
    //   std::cout << info.pack_flag << std::endl;
    //   printf("提取到info信息\n");
    //   // 3. 判断是否被压缩
    //   if (info.pack_flag == true)
    //   {
    //     printf("在解压缩文件\n");
    //     // 4. 如果被压缩,需要进行解压缩,同时修改备份信息
    //     FileUtil fu(info.packpath);
    //     fu.UnCompress(info.real_path);
    //     fu.Remove();
    //     info.pack_flag = false;
    //     _data->update(info);
    //     printf("解压缩完成\n");
    //   }
    //   bool retrans = false;
    //   std::string old_etag;
    //   if (req.has_header("If-Range") == true)
    //   {
    //     old_etag = req.get_header_value("If-Range");
    //     // 有If-Range字段且,这个字段的值与请求文件的最新etag一致则符合断点续传
    //     if (old_etag == GetETag(info))
    //     {
    //       retrans = true;
    //     }
    //   }
    //   printf("retrans:%d\n", retrans);
    //   // 5. 填充rsp 设置响应头部字段: ETag, Accept-Ranges: bytes
    //   printf("填充rsp中\n");
    //   FileUtil fu(info.real_path);
    //   if (retrans == false)
    //   {
    //     fu.GetContent(rsp.body);
    //     rsp.set_header("Accept-Ranges", "bytes");                   // 告诉客户端支持断点重传功能
    //     rsp.set_header("ETag", GetETag(info));                      // etag 是一个标识文件的数据
    //     rsp.set_header("Content-Type", "application/octet-stream"); //  告诉客户实际返回的内容的内容类型
    //     rsp.status = 200;
    //   }
    //   else
    //   {
    //     // 需要进行断点续传
    //     // httplib内部实现了对于区间请求也就是断点续传请求的处理
    //     // 只需要我们用户将文件所有数据读取到rsp.body中,它内部会自动根据请求
    //     // 区间,从body中取出指定区间数据进行响应
    //     //   也就是说,下边的代码可以省略,但是我们需要知道httplib 库给我做了什么工作
    //     fu.GetContent(rsp.body);
    //     rsp.set_header("Accept-Ranges", "bytes");
    //     rsp.set_header("ETag", GetETag(info));
    //     rsp.set_header("Content-Type", "application/octet-stream");
    //     // rsp.set_header("Content-Range", "bytes start-end/fsize");
    //     rsp.status = 206;
    //   }
    //   printf("Download 请求完毕\n");
    //   return;
    // }

    static void Download(const httplib::Request &req, httplib::Response &rsp)
    {
      // 1. 获取客户端请求的资源路径path   req.path
      // 2. 根据资源路径,获取文件备份信息
      printf("收到一个Download请求\n");
      BackupInfo info;
      _data->GetOneByURL(req.path, &info);
      // 3. 判断文件是否被压缩,如果被压缩,要先解压缩,
      if (info.pack_flag == true)
      {
        FileUtil fu(info.packpath);
        fu.UnCompress(info.real_path); // 将文件解压到备份目录下
        // 4. 删除压缩包,修改备份信息(已经没有被压缩)
        fu.Remove();
        info.pack_flag = false;
        _data->update(info);
      }

      bool retrans = false;
      std::string old_etag;
      if (req.has_header("If-Range"))
      {
        old_etag = req.get_header_value("If-Range");
        // 有If-Range字段且,这个字段的值与请求文件的最新etag一致则符合断点续传
        if (old_etag == GetETag(info))
        {
          retrans = true;
        }
      }

      printf("retrans:%d\n", retrans);
      // 4. 读取文件数据,放入rsp.body中
      FileUtil fu(info.real_path);
      if (retrans == false)
      {
        fu.GetContent(rsp.body);
        // 5. 设置响应头部字段: ETag, Accept-Ranges: bytes
        rsp.set_header("Accept-Ranges", "bytes");
        rsp.set_header("ETag", GetETag(info));
        rsp.set_header("Content-Type", "application/octet-stream");
        rsp.status = 200;
      }
      else
      {
        // httplib内部实现了对于区间请求也就是断点续传请求的处理
        // 只需要我们用户将文件所有数据读取到rsp.body中,它内部会自动根据请求
        // 区间,从body中取出指定区间数据进行响应
        //  std::string  range = req.get_header_val("Range"); bytes=start-end
        
        fu.GetContent(rsp.body);
        rsp.set_header("Accept-Ranges", "bytes");
        rsp.set_header("ETag", GetETag(info));
        rsp.set_header("Content-Type", "application/octet-stream");
        // rsp.set_header("Content-Range", "bytes start-end/fsize");
        rsp.status = 206; // 区间请求响应的是206*****
      }
      printf("Download 请求结束\n");
    }

    static void Showlist(const httplib::Request &req, httplib::Response &rsp)
    {
      //  1. 获取所有的文件备份信息
      printf("收到一个showlist请求\n");
      std::vector<BackupInfo> arry;
      _data->GetAll(&arry);
      //std::cout << "文件信息准备完毕,size:" << arry.size() << std::endl;
      //  2. 根据这些文件备份信息组织html页面
      std::stringstream ss;
      ss << "<html><head><title>Download</title></head>";
      ss << "<body><h1>Download</h1><table>";
      for (auto &a : arry)
      {
        ss << "<tr>";
        std::string filename = FileUtil(a.real_path).Filename();
        ss << "<td><a href='" << a.url << "'>" << filename << "</a></td>";
        ss << "<td align='right'>" << totimestring(a.atime) << "</td>";
        ss << "<td align='right'>" << a.fsize / 1024 << "k"
           << "</td>";
        ss << "</tr>";
      }
      ss << "</table></body></html>";
      //std::cout << "文件信息填充完毕,开始填写rsq" << std::endl;
      //  3. 填充rsp响应
      rsp.body = ss.str();
      rsp.status = 200;
      rsp.set_header("Content-Type", "text/html");
      printf("showlist请求完毕\n");
      return;
    }
  };

}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1200204.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【Git】Git分支与应用分支Git标签与应用标签

一&#xff0c;Git分支 1.1 理解Git分支 在 Git 中&#xff0c;分支是指一个独立的代码线&#xff0c;并且可以在这个分支上添加、修改和删除文件&#xff0c;同时作为另一个独立的代码线存在。一个仓库可以有多个分支&#xff0c;不同的分支可以独立开发不同的功能&#xff0…

劲松HPV防治诊疗中心发布:HPV感染全面防治解决方案

在当今社会&#xff0c;HPV(人乳头瘤病毒)感染问题已成为广大公众关注的焦点。作为一种高度传染性的病毒&#xff0c;HPV感染不仅可能导致生殖器疣&#xff0c;还可能引发各种癌症。面对这一严重威胁&#xff0c;劲松HPV防治诊疗中心以其专业的医疗团队、正规的治疗流程和全方位…

操作系统(二)内存管理的基础知识

文章目录 前言内存管理地址空间与地址生成连续内存分配内存碎片连续分配算法碎片整理 非连续内存分配虚拟内存管理虚拟内存地址内存分段内存分页段页式内存管理虚拟内存的覆盖技术虚拟内存的交换技术 缺页异常内存页面置换算法局部页面置换算法Belady现象全局页面置换算法抖动和…

【蓝桥杯选拔赛真题66】Scratch画图机器人 少儿编程scratch图形化编程 蓝桥杯创意编程选拔赛真题解析

目录 scratch画图机器人 一、题目要求 编程实现 二、案例分析 1、角色分析

云原生 黑马Kubernetes教程(K8S教程)笔记——第一章 kubernetes介绍——Master集群控制节点、Node工作负载节点、Pod控制单元

参考文章&#xff1a;kubernetes介绍 文章目录 第一章 kubernetes介绍1.1 应用部署方式演变传统部署&#xff1a;互联网早期&#xff0c;会直接将应用程序部署在物理机上虚拟化部署&#xff1a;可以在一台物理机上运行多个虚拟机&#xff0c;每个虚拟机都是独立的一个环境&…

VUE Slot

在某些场景中&#xff0c;我们可能想要为子组件传递一些模板片段&#xff0c;让子组件在它们的组件中渲染这些片段. <template><h3>ComponentA</h3><ComponentB><h3>插槽传递视图内容</h3></ComponentB> </template> <scr…

Redis04-分布式锁

目录 Redis实现分布式锁 分布式锁的工作流程 Redis实现分布式锁 Redission的watch dog Redis分布式锁的合理应用 Redis实现分布式锁 在单节点的服务器中&#xff0c;java中的synchronized机制是处于JVM层面的&#xff0c;只能保证线程之间的同步。而实际的服务部署中&…

Spring面试题:(六)Spring注解开发原理

ioc过程 发现只要将bean注册到BeanDefinitionMap中就可以创建bean对象 如何将xml配置的bean注册到BeanDefinitionMap 通过注解注册的bean过程一样 注册bean的接口&#xff1a;BeanDefinitionRegistryPostProcessor 开启组件扫描的两种方式&#xff1a;xml和注解 xml方式…

Unity之NetCode多人网络游戏联机对战教程(8)--玩家位置同步

文章目录 前言添加相机玩家添加对应组件服务端权威&#xff08;server authoritative&#xff09;客户端权威&#xff08;client authoritative&#xff09;服务端同步位置阅读与理解PlayerTransformSync.csNetworkVariableUploadTransformSyncTransform 后话 前言 承接上篇&a…

vColorPicker与vue3-colorPicker——基于 Vue 的颜色选择器插件

文章目录 前言样例特点 一、使用步骤&#xff1f;1. 安装2.引入3.在项目中使用 vcolorpicker 二、选项三、事件四、问题反馈问题所在安装引入例子效果图 前言 vColorPicker——官网 vColorPicker——GitHub 样例 vColorPicker是基于 Vue 的一款颜色选择器插件&#xff0c;仿照…

主题模型LDA教程:主题数选取 困惑度perplexing

文章目录 LDA主题数困惑度1.概率分布的困惑度2.概率模型的困惑度3.每个分词的困惑度 LDA主题数 LDA作为一种无监督学习方法&#xff0c;类似于k-means聚类算法&#xff0c;需要给定超参数主题数K&#xff0c;但如何评价主题数的优劣并无定论&#xff0c;一般采取人为干预、主题…

电子工程师的焊接技法总结

基础学习视频如下&#xff1a; 1 老司机焊接纯干货分享&#xff0c;让你焊接不迷路&#xff0c;很适合零基础小白_哔哩哔哩_bilibili 焊接常用工具 1 焊锡丝 按照粗细来分的话&#xff0c;有粗焊锡&#xff0c;有细焊锡&#xff0c;细焊锡一般适合比较精细的焊接。 按照是否含铅…

吃透 Spring 系列—Web部分

目录 ◆ Spring整合web环境 - Javaweb三大组件及环境特点 - Spring整合web环境的思路及实现 - Spring的web开发组件spring-web ◆ web层MVC框架思想与设计思路 ◆ Spring整合web环境 - Javaweb三大组件及环境特点 在Java语言范畴内&#xff0c;web层框架都是基于J…

win环境Jenkins部署前端项目

今天分享win环境Jenkins部署前端vue项目&#xff0c;使用的版本jenkins版本Jenkins 2.406版本。 前提是jenkins安装好了&#xff0c;通用配置已经配置好了&#xff0c;可以参考上两篇博客。 1、前端项目依赖nodejs&#xff0c;需要安装相关插件 点击进入 安装成功标准 jenki…

Home Assistant使用ios主题更换背景

Home Assistant使用ios主题、更换背景 lovelace-ios-dark-mode-theme 默认前置情况&#xff0c;1、已安转HACS插件2、搜索安装 IOS Dark Mode Theme1&#xff09;第一、二步应该很容易实现&#xff0c;configuration.yaml文件很容易被找到2&#xff09;而本人在进行第三步操作时…

在vue3中使用Element-plus的图标

首先安装Element-Plus-icon # 选择一个你喜欢的包管理器# NPM $ npm install element-plus/icons-vue # Yarn $ yarn add element-plus/icons-vue # pnpm $ pnpm install element-plus/icons-vue 如何使用 Element-Plus-icon官方文档链接Icon 图标 | Element Plus (element-…

Zyxel NBG2105 身份验证绕过

直接访问如下payload则会以管理员身份跳转到 home.htm页面 ​​/login_ok.htm漏洞证明 查看本页面的cookie&#xff0c;login为1 文笔生疏&#xff0c;措辞浅薄&#xff0c;望各位大佬不吝赐教&#xff0c;万分感谢。 免责声明&#xff1a;由于传播或利用此文所提供的信息、…

antlr4踩坑记录

一. syntax error: ‘<’ came as a complete surprise to me while matching alternative 参考这个issue&#xff0c;antlr版本必须得是4.6 下载链接&#xff1a;http://www.antlr.org/download/antlr-4.6-complete.jar 二.org.antlr.v4.analysis.LeftRecursiveRuleTrans…

如何在ModelScope社区魔搭下载所需的模型

本篇文章介绍如何在ModelScope社区下载所需的模型。 若您需要在ModelScope平台上有感兴趣的模型并希望能下载至本地&#xff0c;则ModelScope提供了多种下载模型的方式。 使用Library下载模型 若该模型已集成至ModelScope的Library中&#xff0c;则您只需要几行代码即可加载…

【Vue3】scoped 和样式穿透

我们使用很多 vue 的组件库&#xff08;element-plus、vant&#xff09;&#xff0c;在修改样式的时候需要进行其他操作才能成功更改样式&#xff0c;此时就用到了样式穿透。 而不能正常更改样式的原因就是 scoped 标记。 scoped 的渲染规则&#xff1a; <template>&l…