Linux(CentOS)/Windows-C++ 云备份项目(服务器网络通信模块,业务处理模块设计,断点续传设计)

07-17 1078阅读

此模块将网络通信模块和业务处理模块进行了合并

  1. 网络通信通过httplib库搭建完成
  2. 业务处理:
    • 文件上传请求:备份客户端上传的文件,响应上传成功
    • 客户端列表请求:客户端请求备份文件的请求页面,服务器响应
    • 文件下载请求:通过展示的文件列表,点击下载,服务器响应下载的文件数据

文章目录

  • 1. 网络通信模块设计
  • 2. 业务处理模块设计
    • 文件上传业务处理 /upload请求
    • 展示备份文件页面 / /listshow请求
    • 文件下载业务处理 /download
      • 断点续传原理:
      • 3. 服务器代码:
      • 4. 代码位置

        1. 网络通信模块设计

        文件下载Http请求部分如下:通过分隔符可以取到文件数据和文件的其他信息,文件信息和文件内容之间空行隔开。这个解析过程httplib库已经封装完毕

        Linux(CentOS)/Windows-C++ 云备份项目(服务器网络通信模块,业务处理模块设计,断点续传设计)

        网络通信请求设计:

        1. 文件上传:服务器收到/upload 是为文件上传
        2. 展示页面:服务器收到/listshow是服务器所有备份文件展示

          响应 HTTP/1.1 200 OK + 构造html正文界面

        3. 文件下载:服务器收到/download/文件名 为文件下载请求

          响应 HTTP/1.1 200 OK +文件数据(正文)

        2. 业务处理模块设计

        文件上传业务处理 /upload请求

        #pragma once
        #include "backups.hpp"
        #include "./httplib/httplib.h"
        #include "./config/config.hpp"
        extern CloudBackups::DataMange *dataMange;
        namespace CloudBackups
        {
            class Server
            {
            private:
                int port;
                std::string ip;
                std::string download_prefix;
                httplib::Server server;
                // 上传文件
                static void Upload(const httplib::Request &request, httplib::Response &response)
                {
                    LOG(INFO, "upload begin");
                    // POST请求,文件数据在http正文中,分区存储
                    bool ret = request.has_file("file"); // 判断有无上传文件字段
                    if (ret == false)
                    {
                        LOG(ERROR, "request error!");
                        response.status = 400;
                        return;
                    }
                    // 获取数据
                    const auto &file = request.get_file_value("file");
                    std::string backdir = Config::GetInstance()->GetBackDir();
                    // 保存文件
                    std::string filepath = backdir + FileUtil(file.filename).filename(); // 实际路径+文件名
                    FileUtil stream(filepath);
                    stream.setContent(file.content);
                    // 更新文件信息Json文件
                    BackupInfo info(filepath);
                    dataMange->Insert(info);
                    LOG(INFO, "upload success");
                }
                // 展示页面
                static void ListShow(const httplib::Request &request, httplib::Response &response)
                {
                }
                // 下载文件
                static void Download(const httplib::Request &request, httplib::Response &response)
                {
                }
            public:
                Server()
                {
                    Config *config = Config::GetInstance();
                    port = config->GetServerPort();
                    ip = config->GetServerIp();
                    download_prefix = config->GetDownloadPrefix();
                    LOG(INFO, "init server success");
                }
                bool RunMoudle()
                {
                    LOG(INFO, "server running");
                    // 搭建Http服务器
                    server.Post("/upload", Upload); // 文件上传
                    server.Get("/list", ListShow);  // 展示页面
                    server.Get("/", ListShow);      // 网页根目录也是展示页面
                    std::string download_url = download_prefix + "(.*)";
                    server.Get(download_url, Download); // 下载文件,正则表达式捕捉要下载的文件
                    if (server.listen(ip, port) == false)
                    {
                        LOG(FATAL, "server listen failed! ip=" + ip);
                        return false;
                    }
                    return true;
                }
            };
        }
        

        单元测试运行截图

        // #include "util/fileutil.hpp"
        #include 
        #include "util/json.hpp"
        #include "config/config.hpp"
        #include "backups.hpp"
        #include "hot.hpp"
        #include "server.hpp"
        CloudBackups::DataMange *dataMange;
        void ServerUtilTest()
        {
            CloudBackups::Server server;
            dataMange = new CloudBackups::DataMange();
            server.RunMoudle();
        }
        int main(int argc, char const *argv[])
        {
            ServerUtilTest();
            return 0;
        }
        

        Linux(CentOS)/Windows-C++ 云备份项目(服务器网络通信模块,业务处理模块设计,断点续传设计)

        上传文件的信息Json如下:

        Linux(CentOS)/Windows-C++ 云备份项目(服务器网络通信模块,业务处理模块设计,断点续传设计)

        展示备份文件页面 / /listshow请求

        #pragma once
        #include "backups.hpp"
        #include "./httplib/httplib.h"
        #include "./config/config.hpp"
        extern CloudBackups::DataMange *dataMange;
        namespace CloudBackups
        {
            class Server
            {
            private:
                int port;
                std::string ip;
                std::string download_prefix;
                httplib::Server server;
                // 上传文件
                static void Upload(const httplib::Request &request, httplib::Response &response)
                {
                }
                // 展示页面
                static void ListShow(const httplib::Request &request, httplib::Response &response)
                {
                    LOG(INFO, "list show begin");
                    // 获取所有文件信息
                    std::vector array;
                    dataMange->GetAll(array);
                    // 根据所有文件信息构建http响应
                    std::stringstream ss;
                    ss 
                        std::string filename = FileUtil(info.real_path).filename();
                        ss 
                }
            public:
                Server()
                {
                    Config *config = Config::GetInstance();
                    port = config-GetServerPort();
                    ip = config-GetServerIp();
                    download_prefix = config->GetDownloadPrefix();
                    LOG(INFO, "init server success");
                }
                bool RunMoudle()
                {
                    LOG(INFO, "server running");
                    // 搭建Http服务器
                    server.Post("/upload", Upload); // 文件上传
                    server.Get("/list", ListShow);  // 展示页面
                    server.Get("/", ListShow);      // 网页根目录也是展示页面
                    std::string download_url = download_prefix + "(.*)";
                    server.Get(download_url, Download); // 下载文件,正则表达式捕捉要下载的文件
                    if (server.listen(ip, port) == false)
                    {
                        LOG(FATAL, "server listen failed! ip=" + ip);
                        return false;
                    }
                    return true;
                }
            };
        }
        

        Linux(CentOS)/Windows-C++ 云备份项目(服务器网络通信模块,业务处理模块设计,断点续传设计)

        Linux(CentOS)/Windows-C++ 云备份项目(服务器网络通信模块,业务处理模块设计,断点续传设计)

        文件下载业务处理 /download

        http的ETag头部字段:存储了一个资源的唯一标识

        客户端第一次请求文件时会收到响应信息。

        客户端第二次下载时,客户端会把这个信息发送给服务器,让这个服务器根据这个标识判断这个资源有没有被修改锅。如果没修改过。客户端直接使用缓存区的资源。如果改过则重新修改

        http对ETag字段没有定义,这里设定:

        ETags:文件名称-文件大小-最后修改时间 构成

        ETags字段也用于断点续传,断点续传也需要保证文件没有被修改

        http协议的Accept-Ranges:bytes字段用于表示支持断点续传。数据以字节结尾

        Content-Type:字段决定了浏览器如何处理响应正文,用来区分下载还是html显示。

        Content-Type:application/octet-stream常用于文件下载

        断点续传原理:

        文件下载时由于异常而中断,如果从头下载效率较低,需要将之前传输过的数据效率太低。断点续传的目的为了提高上传效率

        实现:客户端在下载时需要记录当前下载的位置。当下载中断时,下次断点续传时将下载起始位置发送给服务器。服务器收到后仅仅回传客户端需要的数据即可

        如果下载文件后这个文件在服务器上被修改了,这时候需要将文件重新下载

        http中断点续传关键点在于告诉服务器下载区间范围,服务器上要检测这个文件是否被修改。

        http协议的Accept-Ranges:bytes字段用于表示支持断点续传

        ETag文件唯一标识符,客户端收到响应会保存这个信息

        请求:

        GET /download/test.txt HTTP/1.1

        If-Range:“服务端在下载时响应ETag字段搭配使用判断文件是否被修改,常用于恢复下载”

        Range: bytes=100-200(区间范围) 这个字段用来告诉客户端需要的数据范围

        响应:

        HTTP/1.1 206(服务器处理部分get请求) Paritial Content

        ETag:”xxxx“(响应资源的版本标识符,判断文件是否被修改)

        Content-Range: bytes 100-200(范围)

        Accept-Ranges: bytes 字段用于表示支持断点续传

        正文就是对应区间的数据

        真正实现时:cpp-httplib会自动根据请求Range字段对response.body进行切片返回,封装实现。直接把response.body全部设置为文件所有内容即可

        #pragma once
        #include "backups.hpp"
        #include "../httplib/httplib.h"
        #include "../config/config.hpp"
        extern CloudBackups::DataMange *dataMange;
        namespace CloudBackups
        {
            class Server
            {
            private:
                int port;
                std::string ip;
                std::string download_prefix;
                httplib::Server server;
                // ETag为设计者自行指定 ETags:文件名称-文件大小-最后修改时间 构成
                static std::string GetETag(BackupInfo info)
                {
                    std::string etag = FileUtil(info.real_path).filename();
                    etag += "-";
                    etag += std::to_string(info.size);
                    etag += "-";
                    etag += std::to_string(info.mtime);
                    return etag;
                }
                // 下载文件
                static void Download(const httplib::Request &request, httplib::Response &response)
                {
                    // 1. 获取客户端请求资源的路径 request.path
                    // 2. 根据路径获取文件备份信息
                    BackupInfo info;
                    if (dataMange->GetByUrl(request.path, info) == false)
                    {
                        LOG(WARNING, "file /download not found");
                        response.status = 404;
                        return;
                    }
                    // 3. 判断文件是否被压缩,被压缩的话需要先解压缩,删除压缩包,修改备份信息
                    if (info.packflag == true)
                    {
                        // 被压缩,解压到backdir目录浏览
                        FileUtil tool(info.pack_path);
                        tool.unzip(info.real_path);
                        // 删除压缩包
                        tool.removeFile();
                        info.packflag = false;
                        // 修改配置文件
                        dataMange->UpDate(info);
                    }
                    //  4. 读取文件数据放入body中
                    FileUtil tool(info.real_path);
                    tool.getContent(response.body);
                    // 判断断点续传
                    bool retrans = false; // 标记断点续传
                    std::string befetag;
                    if (request.has_header("If-Range"))
                    {
                        // 断点续传 服务端在下载时响应ETag字段搭配使用判断文件是否被修改
                        befetag = request.get_header_value("If-Range");
                        if (befetag == GetETag(info))
                        {
                            // 文件没修改过
                            retrans = true;
                        }
                    }
                    // 没有If-Range字段或者If-Range字段与ETag不匹配,重新下载
                    if (retrans == false)
                    {
                        // 正常下载
                        //  5. 设置响应头部字段ETag Accept-Range字段
                        response.set_header("ETag", GetETag(info));
                        response.set_header("Accept-Ranges", "bytes");
                        response.set_header("Content-Type", "application/octet-stream");
                        response.status = 200;
                    }
                    else
                    {
                        // 断点续传,了解区间范围
                        response.set_header("ETag", GetETag(info));
                        response.set_header("Accept-Ranges", "bytes");
                        response.status = 206; // cpp-httplib会自动根据请求Range字段对response.body进行切片返回,封装实现
                    }
                    LOG(INFO, "download success");
                }
            public:
                Server()
                {
                    Config *config = Config::GetInstance();
                    port = config->GetServerPort();
                    ip = config->GetServerIp();
                    download_prefix = config->GetDownloadPrefix();
                    // 创建文件夹
                    FileUtil tool;
                    tool.mkdir(Config::GetInstance()->GetBackDir());
                    tool.mkdir(Config::GetInstance()->GetPackfileDir());
                    LOG(INFO, "init server success");
                }
                bool RunMoudle()
                {
                    LOG(INFO, "server running");
                    // 搭建Http服务器
                    server.Post("/upload", Upload); // 文件上传
                    server.Get("/list", ListShow);  // 展示页面
                    server.Get("/", ListShow);      // 网页根目录也是展示页面
                    std::string download_url = download_prefix + "(.*)";
                    // LOG(INFO, "DEBUG:" + download_url);
                    server.Get(download_url, Download); // 下载文件,正则表达式捕捉要下载的文件
                    if (server.listen(ip, port) == false)
                    {
                        LOG(FATAL, "server listen failed! ip=" + ip);
                        return false;
                    }
                    return true;
                }
            };
        }
        

        3. 服务器代码:

        #include 
        #include "../util/json.hpp"
        #include "../config/config.hpp"
        #include "backups.hpp"
        #include "hot.hpp"
        #include "server.hpp"
        #include 
        CloudBackups::DataMange *dataMange;
        void ServerRun()
        {
            CloudBackups::Server server;
            dataMange = new CloudBackups::DataMange();
            server.RunMoudle();
        }
        void HotRun()
        {
            dataMange = new CloudBackups::DataMange();
            CloudBackups::HotMange hot;
            hot.RunModule();
        }
        int main(int argc, char const *argv[])
        {
            // 启动热点管理模块
            std::thread hot_thread(HotRun);
            std::thread server_thread(ServerRun);
            hot_thread.join();
            server_thread.join();
            return 0;
        }
        

        4. 代码位置

        至此,项目服务器所有业务处理完毕

        Gitee

        Github

VPS购买请点击我

文章版权声明:除非注明,否则均为主机测评原创文章,转载或复制请以超链接形式并注明出处。

目录[+]