云备份项目--准备阶段

1. 预备

1.1 服务端功能划分

  1. 支持客户端文件上传功能
  2. 支持客户端文件备份列表查看功能
  3. 支持客户端文件下载功能(断点续传)
  4. 热点文件管理功能(对长时间无访问的文件进行压缩存储)

1.2 服务端模块划分

  1. 数据管理模块(管理的备份的文件信息,以便于随时获取)
  2. 网络通信模块(实现与客户端的网络通信)
  3. 业务处理模块(上传,列表,下载(断点续传))
  4. 热点管理模块(对长时间无访问文件进行压缩存储)

1.3 客户端功能划分

  1. 指定文件夹中的文件检测(获取文件夹中有什么文件)
  2. 判断指定的文件是否需要备份(如新增,或上次上传后又修改过,但是已经间隔3秒钟都没有被修改)
  3. 将需要备份的文件上传备份到服务器上

1.4 客户端模块划分

  1. 数据管理模块(备份的文件信息)
  2. 文件检测模块(监控指定的文件夹)
  3. 文件备份模块(上传需要备份的文件数据)

2. 环境搭建

2.1 gcc 升级

确保gcc的版本是7.3及以上的文章来源

# 添加镜像
root@hcss-ecs-3db9:/home/lyf/ # vim /etc/apt/sources.list
root@hcss-ecs-3db9:/home/lyf/ # tail -1 /etc/apt/sources.list
deb [arch=amd64] http://archive.ubuntu.com/ubuntu focal main universe
# 更新镜像
root@hcss-ecs-3db9:/home/lyf/ # apt-get update
# 安装gcc
root@hcss-ecs-3db9:/home/lyf/ # apt-get -y install gcc-7 g++-7
# 设置gcc优先级,这样默认使用的是gcc 7
root@hcss-ecs-3db9:/home/lyf/ # update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-7 50
root@hcss-ecs-3db9:/home/lyf/ # update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-10 30
root@hcss-ecs-3db9:/home/lyf/ # update-alternatives --install /usr/bin/gcc gcc /usr/bin/gcc-11 20
# 设置g++优先级
root@hcss-ecs-3db9:/home/lyf/ # update-alternatives  --install /usr/bin/g++ g++ /usr/bin/g++-7 50
root@hcss-ecs-3db9:/home/lyf/ # update-alternatives --install /usr/bin/gcc gcc /usr/bin/g++-10 30
root@hcss-ecs-3db9:/home/lyf/ # update-alternatives --install /usr/bin/gcc gcc /usr/bin/g++-11 20

2.2 安装所需的库

安装jsoncpp库

# 安装
apt-get install libjsoncpp-dev
# 查看一下头文件
root@hcss-ecs-3db9:~# ls /usr/include/jsoncpp/json/
allocator.h  assertions.h  config.h  forwards.h  json_features.h  json.h  reader.h  value.h  version.h  writer.h

下载bundle数据压缩库和httplib库

# 建一个lib目录,进入目录
git clone https://github.com/r-lyeh-archived/bundle.git
git clone https://github.com/yhirose/cpp-httplib.git
# 查看
lyf@hcss-ecs-3db9:~/lib$ ls
bundle  cpp-httplib

3. 简单使用第三方库

3.1 jsoncpp

3.1.1 介绍

JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。它基于JavaScript的一个子集,但是独立于语言,可以被多种编程语言读取。JSON采用文本格式,易于阅读和编写,同时也易于机器解析和生成。

JSON的结构包括:

  1. 键值对:JSON中的每个元素都由键值对组成,键和值之间用冒号(:)分隔,键名必须用双引号括起来。
  2. 数据类型:JSON支持的数据类型包括字符串(String)、数字(Number)、对象(Object)、数组(Array)、布尔值(Boolean)和空值(null)。
  3. 数组:数组在JSON中用方括号([])表示,数组中的元素可以是任何类型的值。
  4. 对象:对象在JSON中用花括号({})表示,对象中的元素是键值对。

一个简单的JSON示例如下:

{
  "name": "John",
  "age": 30,
  "is_student": false,
  "courses": ["Math", "Science", "History"],
  "address": {
    "street": "123 Main St",
    "city": "Anytown",
    "state": "CA"
  }
}

在这个例子中,nameageis_student 是键值对,courses 是一个数组,address 是一个对象。JSON格式的数据可以被JavaScript直接解析,也可以被其他支持JSON的编程语言解析和生成。

3.1.2 常用的类

//Json数据对象类
class Json::Value{
    Value &operator=(const Value &other); 			//Value重载了[]和=,因此所有的赋值和获取数据都可以通过
    Value& operator[](const std::string& key);		//简单的⽅式完成添加元素 val["姓名"] = "⼩明";
    Value& operator[](const char* key);
    Value removeMember(const char* key);			//移除元素
    const Value& operator[](ArrayIndex index) const; //获取数组元素 val["成绩"][0]
    Value& append(const Value& value);				//添加数组元素val["成绩"].append(88);
    ArrayIndex size() const;						//获取数组元素个数 val["成绩"].size();
    std::string asString() const;					//转string   string name =
    val["name"].asString();
    const char* asCString() const;					//转char*    char *name =
    val["name"].asCString();
    Int asInt() const;								//转int int age = val["age"].asInt();
    float asFloat() const;							//转float
    bool asBool() const;							//转 bool
};
/*------------------------------序列化类------------------------------------------*/
//json序列化类,低版本⽤这个更简单
class JSON_API Writer {
	virtual std::string write(const Value& root) = 0;
}
class JSON_API FastWriter : public Writer {
	virtual std::string write(const Value& root);
}
class JSON_API StyledWriter : public Writer {
	virtual std::string write(const Value& root);
}
//json序列化类,⾼版本推荐,如果⽤低版本的接⼝可能会有警告
class JSON_API StreamWriter {
	virtual int write(Value const& root, std::ostream* sout) = 0;
}
class JSON_API StreamWriterBuilder : public StreamWriter::Factory {
	virtual StreamWriter* newStreamWriter() const;
}/*------------------------------反序列化类------------------------------------------*/
//json反序列化类,低版本⽤起来更简单
class JSON_API Reader {
	bool parse(const std::string& document, Value& root, bool collectComments = true);
}
//json反序列化类,⾼版本更推荐
class JSON_API CharReader {
	virtual bool parse(char const* beginDoc, char const* endDoc, Value* root, std::string* errs) = 0;
}
class JSON_API CharReaderBuilder : public CharReader::Factory {
	virtual CharReader* newCharReader() const;
}

3.1.3 使用

#include <iostream>
#include <jsoncpp/json/json.h>
#include <string>
#include <sstream>
#include <memory>

int main()
{
    /* 序列化 */
    std::string name = "zhangsan";
    int age = 20;
    int scores[] = {80, 90, 100};
    // 给数据对象类添加数据
    Json::Value value;
    value["name"] = name;
    value["age"] = age;
    value["score"].append(scores[0]);
    value["score"].append(scores[1]);
    value["score"].append(scores[2]);
    // 序列化类
    Json::StreamWriterBuilder swb;
    std::unique_ptr<Json::StreamWriter> sw(swb.newStreamWriter());
    std::stringstream sout;
    sw->write(value, &sout);        // 注意这里是指针
    std::string s = sout.str();
    printf("序列化结果:\n %s\n%s\n", s.c_str(), "========================================");

    /* 反序列化, 将s反序列化*/
    Json::CharReaderBuilder crb;
    std::unique_ptr<Json::CharReader> cr(crb.newCharReader());
    Json::Value oValue;
    std::string errs;
    bool ret = cr->parse(s.c_str(), s.c_str() + s.size(), &oValue, &errs);
    if(ret == 0) {
        std::cerr << "somethin wrong: " << errs << std::endl;
        return -1;
    } else {
        printf("反序列化结果: \nname: %s\nage: %d\n", 
            (oValue["name"].asString()).c_str(), oValue["age"].asInt());
        for(int i = 0; i < 3; ++i) {
            printf("成绩%d: %d\n", i, oValue["score"][i].asInt());
        }
    }
    return 0;
}

image-20241215213339080

3.2 bundle库

github链接

3.2.1 介绍

BundleBundle是⼀个嵌⼊式压缩库,⽀持23种压缩算法和2种存档格式。使⽤的时候只需要加⼊两个⽂件bundle.hbundle.cpp即可。

namespace bundle
{
  // low level API (raw pointers)
  bool is_packed( *ptr, len );
  bool is_unpacked( *ptr, len );
  unsigned type_of( *ptr, len );
  size_t len( *ptr, len );
  size_t zlen( *ptr, len );
  const void *zptr( *ptr, len );
  bool pack( unsigned Q, *in, len, *out, &zlen );
  bool unpack( unsigned Q, *in, len, *out, &zlen );

  // medium level API, templates (in-place)
  bool is_packed( T );
  bool is_unpacked( T );
  unsigned type_of( T );
  size_t len( T );
  size_t zlen( T );
  const void *zptr( T );
  bool unpack( T &, T );
  bool pack( unsigned Q, T &, T );

  // high level API, templates (copy), 主要使用这两个函数
  T pack( unsigned Q, T );
  T unpack( T );
}

下面是可以选择的压缩算法的排名

RankCompression ratioFastest compressorsFastest decompressorsAverage speedMemory efficiency
1st91.15% ZPAQ958.18MB/s RAW2231.20MB/s RAW1340.63MB/s RAWtbd
2nd90.71% MCM358.41MB/s LZ4F993.68MB/s LZ4508.50MB/s LZ4Ftbd
3rd90.02% TANGELO240.87MB/s SHRINKER874.83MB/s LZ4F334.57MB/s SHRINKERtbd
4th88.31% BSC223.28MB/s LZJB547.62MB/s SHRINKER267.57MB/s LZJBtbd
5th87.74% LZMA25210.74MB/s ZSTDF382.52MB/s MINIZ246.66MB/s ZSTDFtbd
6th87.74% LZIP159.59MB/s SHOCO380.39MB/s ZSTD209.32MB/s SHOCOtbd
7th87.63% BROTLI1140.19MB/s ZLING333.76MB/s LZJB65.40MB/s ZLINGtbd
8th87.50% CSC2033.67MB/s CRUSH304.06MB/s SHOCO60.29MB/s CRUSHtbd
9th87.15% BCM13.73MB/s ZSTD297.34MB/s ZSTDF26.51MB/s ZSTDtbd
10th86.44% ZMOLLY09.00MB/s BSC287.83MB/s CRUSH13.44MB/s BZIP2tbd
11th86.17% LZMA2008.51MB/s BZIP2287.58MB/s BROTLI911.51MB/s BROTLI9tbd
12th86.05% BROTLI906.77MB/s ZMOLLY246.88MB/s BROTLI1110.78MB/s BSCtbd
13th85.27% BZIP205.87MB/s BROTLI9175.54MB/s ZLING08.13MB/s LZ4tbd
14th85.24% ZSTD05.21MB/s BCM118.49MB/s LZMA2507.24MB/s MINIZtbd
15th82.89% ZLING04.08MB/s LZ4108.71MB/s LZMA2006.73MB/s ZMOLLYtbd
16th81.68% MINIZ03.65MB/s MINIZ72.72MB/s CSC2005.27MB/s LZMA20tbd
17th77.93% ZSTDF02.70MB/s LZMA2057.05MB/s LZIP04.90MB/s LZMA25tbd
18th77.57% LZ402.50MB/s LZMA2531.88MB/s BZIP204.83MB/s CSC20tbd
19th77.37% CRUSH02.50MB/s CSC2013.44MB/s BSC04.65MB/s BCMtbd
20th67.30% SHRINKER02.25MB/s MCM06.68MB/s ZMOLLY04.13MB/s LZIPtbd
21th63.30% LZ4F02.14MB/s LZIP04.20MB/s BCM02.29MB/s MCMtbd
22th59.37% LZJB01.15MB/s TANGELO02.34MB/s MCM01.17MB/s TANGELOtbd
23th06.42% SHOCO00.24MB/s BROTLI1101.18MB/s TANGELO00.48MB/s BROTLI11tbd
24th00.00% RAW00.23MB/s ZPAQ00.21MB/s ZPAQ00.22MB/s ZPAQtbd

3.2.1 使用

从一个文件里读取,压缩后将内容写到另一个文件中。

#include <iostream>
#include <fstream>
#include <string>
#include "bundle.h"

int main(int argc, char* argv[])
{
    if(argc != 3) {
        std::cout << "usage:\r\n" << "origin.xxx compressed.xxx\n";
        return -1;
    }

    std::ifstream originFile(argv[1], std::ios::binary);       // 以二进制的方式打开文件

    if(originFile.is_open() == false) {
        std::cerr << "open originFile error\n";
        return -2;
    }

    originFile.seekg(0, std::ios::end);     // 跳转到文件末尾
    size_t fileSz = originFile.tellg();     // 获得文件的大小
    originFile.seekg(0, originFile.beg);    // 跳转到文件开始
    std::string content;
    content.resize(fileSz);
    originFile.read((char*)content.c_str(), fileSz);    // 读取文件数据到conten中
    // 以LZIP方式压缩文件内容
    std::string compressedContent = bundle::pack(bundle::LZIP, content);
    // 打开需要压缩的文件
    std::ofstream compressedFile(argv[2], std::ios::binary);

    if(compressedFile.is_open() == false) {
        std::cerr << "open compressedFile error\n";
        return -2;
    }

    // 将压缩后的数据写入到文件中
    compressedFile.write((char*)compressedContent.c_str(), compressedContent.size());
    originFile.close();
    compressedFile.close();    
    return 0;
}
# 编译程序后选择一个文件,开始压缩,注意数据容量变化
lyf@hcss-ecs-3db9:~/pro/pro24_12_15库的学习$ ls -lh
total 8.7M
-rwxrwxr-x 1 lyf lyf 3.3M Dec 16 20:55 a.out
-rw-rw-r-- 1 lyf lyf 5.4M Dec 15 22:52 bundle.cpp
-rw-rw-r-- 1 lyf lyf  29K Dec 15 22:52 bundle.h
-rw-rw-r-- 1 lyf lyf 1.5K Dec 15 21:34 jsoncpp.cc
-rw-rw-r-- 1 lyf lyf 1.3K Dec 16 20:29 testBundle.cc
lyf@hcss-ecs-3db9:~/pro/pro24_12_15库的学习$ ./a.out bundle.cpp bundle.lz
lyf@hcss-ecs-3db9:~/pro/pro24_12_15库的学习$ ls -lh
total 9.3M
-rwxrwxr-x 1 lyf lyf 3.3M Dec 16 20:55 a.out
-rw-rw-r-- 1 lyf lyf 5.4M Dec 15 22:52 bundle.cpp
-rw-rw-r-- 1 lyf lyf  29K Dec 15 22:52 bundle.h
-rw-rw-r-- 1 lyf lyf 668K Dec 16 20:56 bundle.lz
-rw-rw-r-- 1 lyf lyf 1.5K Dec 15 21:34 jsoncpp.cc
-rw-rw-r-- 1 lyf lyf 1.3K Dec 16 20:29 testBundle.cc

从一个压缩文件中读取数据,解压后,写给一个正常的文件

#include <iostream>
#include <fstream>
#include <string>
#include "bundle.h"

int main(int argc, char* argv[])
{
    if(argc != 3) {
        std::cout << "usage:\r\n" << "compressed.xxx origin.xxx\n";
        return -1;
    }

    // 打开被压缩的文件
    std::ifstream compressedFile(argv[1], std::ios::binary);
    
    if(compressedFile.is_open() == false) {
        std::cerr << "open compressedFile error\n";
        return -1;
    }

    // 读取文件的内容
    compressedFile.seekg(0, std::ios::end);
    size_t fileSz = compressedFile.tellg();
    compressedFile.seekg(0, std::ios::beg);
    std::string content(fileSz, '0');
    compressedFile.read((char*)content.c_str(), fileSz);
    // 解压
    std::string unCompressed = bundle::unpack(content);
    // 打开需要放置解压后的数据的原始文件
    std::ofstream originFile(argv[2], std::ios::binary);

    if(originFile.is_open() == false) {
        std::cerr << "open originFile error\n";
        return -2;
    }

    // 向文件写内容
    originFile.write(unCompressed.c_str(), unCompressed.size());
    compressedFile.close();
    originFile.close();
    return 0;   
}
lyf@hcss-ecs-3db9:~/pro/pro24_12_15库的学习$ ./a.out bundle.lz bundle2.cpp
lyf@hcss-ecs-3db9:~/pro/pro24_12_15库的学习$ ls -lh
total 15M
-rwxrwxr-x 1 lyf lyf 3.3M Dec 16 21:32 a.out
-rw-rw-r-- 1 lyf lyf 5.4M Dec 16 21:33 bundle2.cpp
-rw-rw-r-- 1 lyf lyf 5.4M Dec 15 22:52 bundle.cpp
-rw-rw-r-- 1 lyf lyf  29K Dec 15 22:52 bundle.h
-rw-rw-r-- 1 lyf lyf 668K Dec 16 20:56 bundle.lz
-rw-rw-r-- 1 lyf lyf 1.5K Dec 15 21:34 jsoncpp.cc
-rw-rw-r-- 1 lyf lyf 2.4K Dec 16 21:32 testBundle.cc
# 比较两个文件的md5值
lyf@hcss-ecs-3db9:~/pro/pro24_12_15库的学习$ md5sum bundle.cpp 
4cb64c7a8354c82402dd6fe080703650  bundle.cpp
lyf@hcss-ecs-3db9:~/pro/pro24_12_15库的学习$ md5sum bundle2.cpp 
4cb64c7a8354c82402dd6fe080703650  bundle2.cpp

3.3 httplib库

3.3.1 介绍和常用的类

httplib 库,⼀个 C++11 单⽂件头的跨平台 HTTP/HTTPS 库。安装起来⾮常容易。只需包含httplib.h在你的代码中即可。
httplib 库实际上是⽤于搭建⼀个简单的 http 服务器或者客⼾端的库,这种第三⽅⽹络库,可以让我们免去搭建服务器或客⼾端的时间,把更多的精⼒投⼊到具体的业务处理中,提⾼开发效率。

下面所有的类和结构体都在namespace httplib{}里面

下面是Request结构体需要用到的属性和方法

struct MultipartFormData {
    std::string name;
    std::string content;
    std::string filename;
	std::string content_type;
}
using MultipartFormDataItems = std::vector<MultipartFormData>;

struct Request {
    std::string method;   // 请求方法
    std::string path;	  // 资源路径
    Headers headers;      // 头部字段
    std::string body;     // 正文部分
    // for server
    std::string version;  // 协议版本
    Params params;	      // 查询字符串?后面的内容
    MultipartFormDataMap files;  // 客户端上传的文件信息
    Ranges ranges;               // 用于实现断点续传的请求文件区间
    bool has_header(const char *key) const;
    std::string get_header_value(const char *key, size_t id = 0) const;
    void set_header(const char *key, const char *val);
    bool has_file(const char *key) const;
    MultipartFormData get_file_value(const char *key) const;
};

/*
Request结构体的组成:
首行: 分为请求方法(GET,POST...)  URL  协议版本
头部: key: val\r\nkey: val\r\n
空行: \r\n
正文:提交给服务器的数据

Request结构体的作用:
  1. 客户端保存的http请求相关信息,最终组织http请求发送给服务器
  2. 服务器收到http请求后进行解析,将解析的数据保存在Requets结构体中,待后续处理
*/

下面是Response结构体需要用到的属性和方法

struct Response {
    std::string version;	// 协议版本
    int status = -1;		// 响应状态码
    std::string reason;
    Headers headers;		// 头部字段
    std::string body;		// 响应给客户端的正文
    std::string location;   // 重定向位置
    void set_header(const char *key, const char *val);		// 设置头部字段
    void set_content(const std::string &s, const char *content_type);	// 设置正文 
};

/*
Response结构体的组成:
首行: 分为请求方法(GET,POST...)  响应码  状态码描述
头部: key: val\r\nkey: val\r\n
空行: \r\n
正文:响应给客户端的数据

Response结构体功能:
  用户将响应数据放到结构体中,httplib会将其中的数据按照http响应格式组织成为http响应,保存在Response结构体中,发送给客户端。
*/

下面是server类

class Server {
    using Handler = std::function<void(const Request &, Response &)>;	// 函数指针类型
    using Handlers = std::vector<std::pair<std::regex, Handler>>;		// 请求与处理函数映射表
    std::function<TaskQueue *(void)> new_task_queue;					// 线程池,用于处理请求
    // 针对某一个路径下各种请求,设置处理的含糊
    Server &Get(const std::string &pattern, Handler handler);
    Server &Post(const std::string &pattern, Handler handler);
    Server &Put(const std::string &pattern, Handler handler);
    Server &Patch(const std::string &pattern, Handler handler);
    Server &Delete(const std::string &pattern, Handler handler);
    Server &Options(const std::string &pattern, Handler handler);
    // 搭建并启动http服务器
    bool listen(const char *host, int port, int socket_flags = 0);
};

/*
Handler:	
	函数指针类型,定义了一个http请求处理回调函数格式。
    httplib搭建的服务器收到请求后,进行解析,得到一个Request结构体,其中包含了请求数据,
	根据请求数据就可以处理这个请求,处理该请求的函数格式就是Handler格式
	Request参数,保存请求数据,让用户能够根据请求数据进行业务处理
	Response参数,需要用户在业务处理中,填充数据,最终要响应给客户端
Handlers:
	是一个请求路由数组,std::vector<std::pair<std::regex, Handler>>,将其看做一个n*2的表
	regex是正则表达式,用于匹配http请求资源路径,Handler请求处理函数指针
	当服务器收到请求解析得到Request后,就会根据资源路径以及请求方法到这个表中查看有没有对行的处理函数,有的话就调用函数,没有就响应404
	说白了,这张表决定了哪个请求用哪个函数处理
new_task_queue:
	是一个线程池,用于处理http请求
	当收到一个新连接后,将新的客户端连接放入线程池中,线程池中该线程要做的工作如下
	1. 接受请求,解析请求,得到Request结构体。
	2. 在Handlers表中找到处理该请求的方法
	3. 处理完毕,根据函数返回的Response结构体中的数据,组织http响应发送给客户端
*/

下面是client类

class Client {
    Client(const std::string &host, int port);		// 传入服务器的ip地址和端口号
    Result Get(const char *path, const Headers &headers);// 向服务器发送get请求
    Result Post(const char *path, const char *body, size_t content_length,const char *content_type);// 向服务器发送post请求
    Result Post(const char *path, const MultipartFormDataItems &items);// post提交读取与数据,常见于文件上传
}

3.3.2搭建一个简单的服务器

下面是服务器代码testServer.cc

#include <iostream>
#include "httplib.h"

void hiHandler(const httplib::Request& req, httplib::Response& resp)
{
    resp.set_content("hi httplib", "text/plain");       // text/plain :纯文本格式
    resp.status = 200;
}

void numsHandler(const httplib::Request& req, httplib::Response& resp)
{
    auto nums = req.matches[1];    // [0]表示整个url,往后下标保存的是捕捉的数据,这里捕捉的是\d+,任意数字
    resp.set_content(nums, "text/plain");
    resp.status = 200;
}

void multipartHandler(const httplib::Request& req, httplib::Response& resp)
{
    auto ret = req.has_file("name1");       // 是否上传名为name1的文件
    if(ret == false) {
        std::cerr << "上传文件失败!\n";
        resp.status = 404;
        return;
    } else {
        const auto& file = req.get_file_value("name1");
        // 设置响应体
        resp.body.clear();
        resp.body = file.filename;
        resp.body += "\n";
        resp.body += file.content;
        // 设置响应头部
        resp.set_header("Content-Type","text/plain");
        // 设置状态码
        resp.status = 200;
    }
}

int main()
{
    httplib::Server svr;
    svr.Get("/hi", hiHandler);                // 处理针对/hi的Get请求
    svr.Get(R"(/nums/(\d+))", numsHandler);   // 根据正则表达式匹配请求路径
    svr.Post("/multipart", multipartHandler);
    svr.listen("0.0.0.0", 9000);              // 0.0.0.0表示监控服务器的所有网卡
    return 0;
}

image-20241217194204048

image-20241217194222780

下面是testClient.cc,自己随便写一个文件内容

#include <iostream>
#include "httplib.h"

#define SEVER_IP "124.70.203.1"
#define SERVER_PORT 9000

int main()
{
    httplib::Client cli("124.70.203.1", SERVER_PORT);
    httplib::MultipartFormData file;
    file.name = "name1";        // 要与服务器那边的对应上
    file.filename = "hello.txt";
    file.content = "this is hello.txt content, test, test, test....\ntest\ntest\n";
    file.content_type = "text/plain";
    httplib::MultipartFormDataItems items;      // 这是一个MultipartFormData数组
    items.push_back(file);
    if(auto res = cli.Post("/multipart", items)) {
        std::cout << res->status << '\n';
        std::cout << res->body << '\n';
    } else {
        auto err = res.error();
        std::cerr << "HTTP error: " << httplib::to_string(err) << std::endl;
        return 1;
    }
    return 0;
}
lyf@hcss-ecs-3db9:~/pro/pro24_12_15库的学习$ g++ testClient.cc -o client -lpthread -std=c++11
lyf@hcss-ecs-3db9:~/pro/pro24_12_15库的学习$ ./client 
200
hello.txt
this is hello.txt content, test, test, test....
test
test
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值