Linux(Ubuntu)/Windows-C++云备份实现

项目介绍:

项目简介:
实现云备份服务器和客户端程序组成一套系统
客户端程序运行在客户端主机,对指定目录下需要备份的文件进行备份上传;
服务器对上传的文件进行备份存储,并对非热点文件进行压缩存储节省磁盘空间,并且支持浏览器访问以及断点续传下载;
项目特点:
http服务器,线程池,读写锁,lz压缩
项目实现:

  • 客户端:
    文件监控模块:采用c++14中的文件系统接口对目录进行遍历获取文件信息。
    数据管理模块:记录备份的文件信息,并且通过信息判断文件是否需要备份。
    文件上传模块:搭建http客户端,使用post请求方式实现文件上传备份。
  • 服务端:
    文件管理模块:对指定目录下的文件遍历判断是否为非热点文件,并进行非热点文件压缩存储。
    数据管理模块:记录管理备份文件信息,实现判断文件是否已经压缩的功能。
    HTTP服务器模块:与客户端进行交互,提供文件上传,查看,下载(断点续传)功能的处理。

在这里插入图片描述

概要设计:

一、客户端

  • 编程环境:Windows 下的客户端程序:Win10-Visual Studio2017;
  • 功能需求:监控目录,自动对指定目录下需要备份的文件进行上传备份;

1.目录监控: 用 C++17filesystem 遍历目录,判断文件是否需要备份,判断方式、条件如下;

  • 获取历史备份信息,获取当前监控目录下文件信息(名称、时间、大小);
  • 判断是否需要备份:当前文件信息与同名历史文件信息不符则需要备份,文件信息未在历史信息中出现需要备份;

2.网络通信: 遍历需要上传备份的文件,获取其文件名称,使用 httplib 库中提供的接口进行文件数据上传;

3.**数据管理:**从第一步中获取到需要备份的文件,将其信息保存到下来(代码内:map,代码外:文件),保存格式为【文件名称 : 创建时间 + 文件大小】的键值对,被保存的文件也相当于历史备份信息,可作为用来判断文件是否需要备份的比对;

二、服务端

  • 编程环境:Linux服务器:Ubuntu18.04.1-vim/g++/makefile ;
  • 功能需求:对客户端上传的文件进行保存,并对文件进行检查,如果是非热点文件,那么就进行压缩存储,删除源文件,支持用户浏览器对备份文件的访问与下载;

1.网络通信: 使用 httplib 库中提供的接口进行接收客户端上传的文件数据,并以文件形式保存;

2.浏览下载: 用户通过浏览器可以查看当前已备份文件,可以对备份文件进行下载,并且下载还支持断点续传;

3.文件压缩: 备份文件在一定时间内没有被访问过,那么该文件就是非热点文件,对其进行压缩存储,判断方法就是用当前时间减去文件最后一次访问时间;

3.数据管理: 压缩存储的文件,会将其对应的原文件删除,所以我们需要保留原文件与压缩文件之间的对应关系(代码内:map,代码外:文件),保存格式为【原文件 : 压缩文件】的键值对;

技术调研

目录监控

  • 1.需要获取目录下所有的文件信息,使用c++17filesystem中的文件遍历功能
  • 2.根据文件信息判断是否需要备份 ①.是否新增;②.是否修改
    程序第一次启动,所有文件都需要进行备份,并且记录信息(文件名称–文件唯一标识(md5–大小+时间))
    之后对获取到的文件信息进行判断:是否在已备份文件信息中 (1).不存在则为新增 (2).存在则通过唯一标识符进行判断是否已修改
  • 3.文件备份
    遍历需要备份的文件信息(获取文件路径名)
    搭建http客户端,上传文件数据,备份到云端

数据管理

  • 代码操作时使用unordered_map进行数据管理;
  • 持久化存储使用文件来进行数据管理。

压缩与解压缩

  • 使用 bundle 实现文件压缩与解压缩;

网络通信

  1. 客户端
  • 创建客户端对象:httplib::Client cli("服务端ip", 服务端port);
  • 实现文件上传与接收:组织httplib::MultipartFormData结构的信息,需要填充的信息有以下四种:

httplib::MultipartFormData data;
data.name:位域信息,根据不同类型的内容设置不同的名字(双方协商),服务端通过这个字段可以判断是否有我需要的信息上传了,而且可以根据这个字段的信息来确定该文件的处理方式;
data.filename:上传文件的真实名称;
data.content:文件内容;
data.content_type:文件格式;

  • 创建httplib::MultipartFormDataItems数组,该数组存放的元素类型为httplib::MultipartFormData,当我们组织好文件信息后,将其添加入该数组中;
  • 使用客户端对象进行上传,上传格式为cli.Post("请求信息", 数组);
  • 客户端通过浏览器进行访问、下载,浏览器请求格式为:ip:port/请求资源
  1. 服务端
  • 创建服务端对象:httplib::Server ser;
  • 为客户端不同的请求信息,创建不同的void(*fun)(const httplib::Request&, httplib::Response&);回调函数;
  • 对于文件上传请求,回调函数如下:
static void Upload(const httplib::Request& req, httplib::Response& res){
       
	//判断上传文件是否存在    
	if(!req.has_file("上传文件的位域信息,也就是name字段")){
   
		//...  
	}    
	//存在则获取文件    
	const httplib::MultipartFormData& file = req.get_file_value("上传文件的位域信息,也就是name字段");    
	//然后获取文件内容,写入文件,记录备份信息...   
	//...
	return;    
} 
  • 对于访问、下载等请求,则是组织相应格式的响应信息即可,需要注意的是,用户访问时展示的是所有备份文件的名字,但是可能有一部分文件已经被压缩存储了,所以下载时需要进行判断,对压缩文件进行解压缩才能下载;
  • 开始监听,格式为:srv.listen("0.0.0.0", 监听port),将监听 IP 设置为 “0.0.0.0” 的目的是,可以监听该主机上任意一个网卡设备的请求,这样做更加便捷可靠;

断点续传

  1. 假设现在用户通过浏览器要下载 xuexi.mp4 文件;
  2. 服务端收到请求后,检查是否为从头开始下载该文件,如果是从头开始下载,则响应信息设置为以下内容:
//回复响应信息的对象为:res
res.set_header("Accept-Ranges", "bytes");//告诉客户端,本服务器支持断点续传
res.set_header("ETag", newflag);//将备份文件的唯一标识返回给客户端,用于后续比较
res.set_header("Content-Type", "application/octet-stream");//下载则设置为二进制传输
res.body = str;//设置响应正文,也就是文件内容
res.status = 200;//成功响应返回200
  1. 客户端在下载的过程中出现了一些意外情况,导致下载中断,此时选择继续下载,因为服务端支持断点续传功能,所以进行断点续传;
  2. 客户端会通过会通过ranges字段向服务端请求需要断点续传的区间是哪些,ranges字段的每一个元素都是pair类型,first-代表了起始位置,second代表了结束位置;
  3. 服务端在收到请求后,判断此次下载为断点续传,则会进行以下操作:
//请求对象为req,响应对象为res
1. 判断该文件是否为断点续传,如果是断点续传,则获取之前传给客户端的文件唯一标识
std::string oldflag;    
if(req.has_header("If-Range")){
        
  	oldflag = req.get_header_value("If-Range");    
}
2. 然后拿当前文件唯一标识和旧的文件唯一表示进行比较,如果不相等,则说明备份文件被修改过了,那么就不能断点续传了,因为内容会接不上,需要全部下载,如果相等,则断点续传
if(req.has_header("If-Range") && newflag == oldflag){
       
    //获取断点续传区间,因为只有一个区间,所以不需要循环获取
    begin = req.ranges[0].first;    
    end = req.ranges[0].second;    
    //如果end==-1,则说明是从begin位置到文件结束    
    if(end == -1){
       
        //修改end为文件末尾,文件大小减一    
        end = filesize - 1;    
    }
3. 设置头部
    3.1 ("Content-Range", "bytes 起始-结束/文件大小")
    std::stringstream ss;    
    ss << "bytes " << begin << '-' << end << '/' << filesize;    
    res.set_header("Content-Range", ss.str());    
    3.2 将备份文件的唯一标识返回给客户端,用于后续比较
    res.set_header("ETag", newflag);
    3.3 下载则设置为二进制传输
	res.set_header("Content-Type", "application/octet-stream");
	res.body = str;//设置响应正文,也就是文件内容 
    res.status = 206;//断点续传成功响应码为206   
}  

详细设计:

目录监控模块

样例编写(c++17的filesystem中的文件遍历功能)

目录遍历的技术选型,以及文件信息的获取(大小,时间属性)
编写目录监控函数代码示例:

#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string>
#include <cstdlib>
#ifdef _WIN32
#include <windows.h>
#include <filesystem>
#else
#include <experimental/filesystem>
#include <unistd.h>
#endif

void ScanDir(const std::string& path)
{
   
    if (!std::experimental::filesystem::exists(path))
    {
      
        std::experimental::filesystem::create_directories(path);
    }   
    for (auto& file : std::experimental::filesystem::directory_iterator(path))
    {
      
        auto& f = file.path();
        if (std::experimental::filesystem::is_directory(f))
        {
      
            std::cout << f.filename() << "/\n";
            continue;
        }   
        else
        {
      
            std::cout << f.filename();
        }   
        //获取文件大小 
        uint64_t size = std::experimental::filesystem::file_size(f);
        std::cout << "\t" << size;
		//获取时间
        auto time_type = std::experimental::filesystem::last_write_time(f);
        std::time_t t = decltype(time_type)::clock::to_time_t(time_type);
        std::cout << "\t" << std::asctime(std::localtime(&t)) << std::endl;
    }
    return;
}
int main()
{
   
	//需要监控的目录
    const char* path = "./testdir/";
    while (1)
    {
   
        ScanDir(path);
#ifdef _WIN32
        Sleep(1000);
#else
        sleep(1);
#endif
    }
    return 0;
}

数据管理模块

在服务端需要对文件信息进行管理,这里采用的是unordered_map进行存储,主要函数有序列化和反序列化。
序列化:对以键值对形式的数据文件信息进行存储,进行写入操作。
反序列化:对之前写入的文件内容读取出来,进行读取操作。

样例编写(unordered_map格式存储)

/*
 *将数据序列化、反序列化样例编写
 */
#include <iostream>
#include <string>
#include <fstream>
#include <vector>
#include <unordered_map>
#include <sstream>
#include <experimental/filesystem>
using namespace std;

//分割字符,str:被分割字符串,sp:分割符,arry:分割后存入数组
int Split(const string &str, const string &sp, vector<string> *arry)
{
   
    int count = 0;
    size_t pos, idx = 0;
    while(1)
    {
      
        pos = str.find(sp, idx);
        if(pos == string::npos)
        {
      
            break;
        }   
        string tmp = str.substr(idx, pos-idx);
        arry->push_back(tmp);
        idx = pos + sp.size();
        count++;
    }   
    if(idx != str.size())
    {
      
        arry->push_back(str.substr(idx));
        count++;
    }
    return count;
}
void unseri()
{
   
    //反序列化,将文件内容读取出来
    const char *name = "test.dat";
    ifstream infile;
    std::string body;
    infile.open(name, std::ios::binary);
    if(infile.is_open() == false)
    {
   
        std::cout << "open file failed!\n";
        return ;
    }
    uint64_t size = std::experimental::filesystem::file_size(name);
    body.resize(size);
    infile.read(&body[0], size);
    infile.close();
    //std::cout << body << std::endl;
    vector<string> arry;
    Split(body, "\n", &arry);
    for(auto &s: arry)
    {
   
        //二次分割,将=两端进行分割输出
        vector<string> kv;
        Split(s, "=", &kv);
        std::cout << "[" << kv[0] << "]" << "=" << "[" << kv[1] << "]\n";
    }
}
void seri()
{
   
    unordered_map<string, string> _map = {
   
        {
   "main.cpp", "9913687567"},
        {
   "child.cpp", "9913676453"},
        {
   "client.cpp", "9913345244"},
        {
   "server.cpp", "9913687565"}
    };
    stringstream ss;
    ss.clear();
    for(auto &info: _map)
    {
   
        ss << info.first << "=" << info.second << "\n";
    }
    ofstream outfile;
    outfile.open("test.dat", std::ios::binary);
    outfile << ss.str();
    outfile.close();
}
int main()
{
   
    //seri();
    unseri();
    return 0;
}

文件压缩与解压缩模块

bundle数据压缩库使用测试

bundle是一个GitHub开源的插件压缩库(https://github.com/r-lyeh-archived/bundle),提供文件的压缩和解压方法。可以直接嵌入到代码中,直接使用,支持23种压缩算法和2种存档格式,使用时只需加入bundle.h和bundle.cpp两个文件即可。
编译时,指明编译bundle.cpp,同时因为bundle.cpp中使用了多线程,还需要链接pthread库

g++ -o test test.cc bundle.cpp -std=c++11 -lptrhead

以下进行编写示例:

  • 压缩函数编写示例
    通过对bundle.cpp文件进行压缩,将压缩后的文件命名为bundle.cpp.lz
void compress()
{
   
    //将bendle.cpp文件内容进行读取,读取到body里面
    const char *name = "./bundle.cpp";
    uint64_t size = std::experimental::filesystem::file_size(name);
    std::string body;
    body.resize(size);
    std::ifstream infile;
    infile.open(name, std::ios
[root@hadoop01 mysql-8.0]# bin/mysqld --initialize --user=mysql --basedir=/opt/software/mysql-8.0 --datadir=/opt/software/mysql-8.0/data bin/mysqld: /lib64/libstdc++.so.6: version `CXXABI_1.3.11' not found (required by bin/mysqld) bin/mysqld: /lib64/libstdc++.so.6: version `CXXABI_1.3.8' not found (required by bin/mysqld) bin/mysqld: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.22' not found (required by bin/mysqld) bin/mysqld: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20' not found (required by bin/mysqld) bin/mysqld: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by bin/mysqld) bin/mysqld: /lib64/libstdc++.so.6: version `CXXABI_1.3.9' not found (required by bin/mysqld) bin/mysqld: /lib64/libm.so.6: version `GLIBC_2.27' not found (required by bin/mysqld) bin/mysqld: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by bin/mysqld) bin/mysqld: /lib64/libc.so.6: version `GLIBC_2.25' not found (required by /opt/software/mysql-8.0/bin/../lib/private/libcrypto.so.3) bin/mysqld: /lib64/libc.so.6: version `GLIBC_2.28' not found (required by /opt/software/mysql-8.0/bin/../lib/private/libprotobuf-lite.so.24.4.0) bin/mysqld: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.20' not found (required by /opt/software/mysql-8.0/bin/../lib/private/libprotobuf-lite.so.24.4.0) bin/mysqld: /lib64/libstdc++.so.6: version `CXXABI_1.3.9' not found (required by /opt/software/mysql-8.0/bin/../lib/private/libprotobuf-lite.so.24.4.0) bin/mysqld: /lib64/libstdc++.so.6: version `CXXABI_1.3.8' not found (required by /opt/software/mysql-8.0/bin/../lib/private/libprotobuf-lite.so.24.4.0) bin/mysqld: /lib64/libstdc++.so.6: version `GLIBCXX_3.4.21' not found (required by /opt/software/mysql-8.0/bin/../lib/private/libprotobuf-lite.so.24.4.0) [root@hadoop01 mysql-8.0]# 这个正确吗?
03-21
<think>好的,用户遇到了在初始化MySQL时出现的依赖库问题。首先,我需要确认这些错误信息的具体原因。用户执行的命令是`bin/mysqld --initialize --user=mysql --basedir=/opt/software/mysql-8.0 --datadir=/opt/software/mysql-8.0/data`,结果报错提示多个库版本不兼容。 首先,我需要理解这些错误信息。例如,`CXXABI_1.3.11`、`GLIBCXX_3.4.22`等缺失,说明系统安装的libstdc++.so.6版本过低。同时,还有GLIBC版本的问题,比如`GLIBC_2.27`和`GLIBC_2.28`未找到,这表明系统的glibc版本不够新。这可能是因为用户使用的Linux发行版比较旧,比如CentOS 7,而MySQL 8.0需要更高版本的依赖库。 接下来,我需要考虑用户可能的环境。用户之前配置了MySQL,安装路径在/opt/software/mysql-8.0,可能使用的是CentOS或RHEL系统。根据错误信息中的路径/lib64,这通常是CentOS/RHEL的结构。CentOS 7默认的glibc版本是2.17,而错误中提到的GLIBC_2.25、2.27、2.28都需要更高的版本。同样,libstdc++的版本可能也较旧。 我需要验证这些库的当前版本。用户可以通过运行`strings /lib64/libstdc++.so.6 | grep GLIBCXX`和`ldd --version`来查看当前安装的版本。如果这些命令显示缺少所需的版本,那么确实是库版本过低的问题。 解决方案方面,用户有几个选择:升级系统到支持更高版本库的发行版,比如CentOS 8或Ubuntu 18.04以上;或者手动升级glibc和libstdc++。不过,手动升级glibc有风险,可能导致系统不稳定。另一个方法是使用静态编译的MySQL版本,或者使用官方预编译的针对较旧系统的版本。 用户可能更倾向于不升级整个系统,所以需要建议替代方案。比如,检查MySQL的下载页面是否有针对较旧系统的兼容版本,或者使用docker容器来运行MySQL 8.0,避免依赖问题。此外,用户可能需要安装高版本的libstdc++,比如通过devtoolset来获取较新的工具链,同时更新库路径。 在回答时,需要分步骤指导用户如何检查当前库版本,解释问题原因,然后提供可行的解决方案,并注意警告用户升级系统库的风险。同时,确保用户理解每个步骤的作用,避免操作失误导致系统问题。</think>--- 您遇到的错误提示表明系统缺少MySQL 8.0所需的高版本依赖库(`glibc` 和 `libstdc++`),**当前配置步骤本身没有错误,但系统环境不符合MySQL 8.0的运行要求**。以下是详细分析和解决方案: --- ### 一、错误原因分析 1. **核心依赖缺失**: - `GLIBC_2.25/2.27/2.28`:MySQL 8.0需要更高版本的GNU C库(`glibc`) - `CXXABI_1.3.x` 和 `GLIBCXX_3.4.2x`:需要高版本C++标准库(`libstdc++`) 2. **系统兼容性问题**: - **典型场景**:您可能在使用较旧的Linux发行版(如CentOS 7/RHEL 7),其默认`glibc`版本为2.17,而MySQL 8.0要求至少**glibc 2.28**。 - **验证当前版本**: ```bash # 查看glibc版本 ldd --version | grep ldd # 查看libstdc++支持的符号版本 strings /lib64/libstdc++.so.6 | grep GLIBCXX ``` --- ### 二、解决方案 #### 方案1:升级系统(推荐) **适用场景**:生产环境或长期使用 - **CentOS/RHEL 7用户**: ```bash # 升级到CentOS 8 Stream(需备份数据) sudo yum install -y centos-release-stream sudo yum distro-sync ``` - **Ubuntu/Debian用户**: ```bash sudo do-release-upgrade ``` #### 方案2:手动安装高版本依赖库(高风险) **适用场景**:临时测试环境 1. **安装高版本glibc**(需源码编译): ```bash wget http://ftp.gnu.org/gnu/glibc/glibc-2.28.tar.gz tar -xzf glibc-2.28.tar.gz cd glibc-2.28 mkdir build && cd build ../configure --prefix=/usr make -j4 && sudo make install ``` **警告**:此操作可能导致系统崩溃,需谨慎操作! 2. **更新libstdc++**: ```bash # 安装devtoolset-10(CentOS 7) sudo yum install -y centos-release-scl sudo yum install -y devtoolset-10 # 激活环境 scl enable devtoolset-10 bash ``` #### 方案3:使用兼容的MySQL版本(最安全) 1. **选择MySQL 5.7**: ```bash wget https://downloads.mysql.com/archives/get/p/23/file/mysql-5.7.43-linux-glibc2.12-x86_64.tar.gz ``` 2. **或使用MySQL官方YUM仓库安装**: ```bash # CentOS 7示例 sudo yum install -y https://dev.mysql.com/get/mysql80-community-release-el7-7.noarch.rpm sudo yum install -y mysql-community-server ``` --- ### 三、验证修复 若选择方案2,请检查依赖库版本: ```bash # 查看glibc版本 strings /lib64/libc.so.6 | grep GLIBC_ # 查看libstdc++版本 strings /lib64/libstdc++.so.6 | grep GLIBCXX ``` --- ### 四、补充建议 1. **使用Docker容器**(避免依赖冲突): ```bash docker run --name mysql8 -e MYSQL_ROOT_PASSWORD=your_pwd -p 3306:3306 -d mysql:8.0 ``` 2. **检查系统架构**: - 确认系统为64位:`uname -m` 应显示 `x86_64` - 若为ARM架构需下载对应版本 --- ### 错误示例与解决对照表 | 错误类型 | 对应解决方案 | |-------------------------|-------------------| | `GLIBC_2.28 not found` | 方案1或方案3 | | `CXXABI_1.3.9 missing` | 方案2(更新C++库)| | 多版本混合依赖冲突 | 方案3(降级MySQL)| --- 通过上述任一方案解决依赖问题后,重新执行初始化命令即可正常启动MySQL。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值