目录
第三方库的使用
jsoncpp库序列化和反序列化
json 是一种数据交换格式,采用完全独立于编程语言的文本格式来存储和表示数据。
比如:一名学生的信息
char name = "小明";
int age = 18;
float score[3] = {88.5, 99, 58};
json这种数据交换格式是将这多种数据对象组织成为一个字符串:
[
{
"姓名" : "小明",
"年龄" : 18,
"成绩" : [88.5, 99, 58]
},
{
"姓名" : "小亮",
"年龄" : 18,
"成绩" : [88.5, 99, 58]
}
]
json 数据类型:对象,数组,字符串,数字
对象:使用花括号{}
括起来的表示一个对象。
数组:使用中括号[]
括起来的表示一个数组。
字符串:使用常规双引号""
括起来的表示一个字符串
数字:包括整形和浮点型,直接使用。
jsoncpp 库用于实现json 格式的序列化和反序列化,完成将多个数据对象组织成为json 格式字符串,以及将json格式字符串解析得到多个数据对象的功能。
jsoncpp实现序列化和反序列化主要通过三个类来实现
//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;
}
jsoncpp序列化
- 将所有的数据保存在Json::Value对象中
- 使用Josn::StreamWriterBulider创建一个Josn::StreamWrite
- 使用Josn::StreamWriter,实现序列化
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;
}
StreamWriter 类的write能够将Json::Value对象转换为Json类型的字符串存放在sout中。(ostream是一个基类,可以使用std::stringstream字符串流来保存)
StremWrtier是一个抽象类不能直接定义出对象,需要先定义一个StreamWriterBuilder类的对象再通过newStreamWriter接口定义StremWrtier对象
jsoncpp反序列化
-
先定义一个Json:Value root
-
使用Josn::CharReaderBuilder创建一个Josn::CharReader
-
使用Josn::CharReader,实现序列化
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;
}
CharReader类的parse接口能够将Json类型的字符串反序列化成Json::Value对象存放到root中。bgeinDoc是字符串的起始位置,endDoc是字符串的结束位置,errs保存错误信息
同样的,CharReader是一个抽象类,不能直接定义出对象,需要先定义一个CharReaderBuilder类的对象,再通过newCharReader接口创建一个 CharReader对象,才能使用parse接口
bundle文件压缩库
BundleBundle 是一个嵌入式压缩库,支持23种压缩算法和2种存档格式。使用的时候只需要加入两个文件bundle.h 和 bundle.cpp 即可。
主要使用两个接口pack和unpack来完成压缩和解压缩
T pack( unsigned Q, T );//按照Q的压缩格式对T中的数据进行解压缩
T unpack( T ); //将T中的数据进行解压缩
压缩格式包括:
文件压缩:
#include<iostream>
#include<string>
#include<fstream>
#include"bundle.h"
int main(int argc,char* argv[])
{
std::cout<<"argv[1] 是原始文件路径名\n";
std::cout<<"argv[2] 是压缩包名称\n";
if(argc<3) return -1;
std::string ifilename=argv[1];
std::string ofilename=argv[2];
std::ifstream ifs;
ifs.open(ifilename,std::ios::binary);//打开原始文件
ifs.seekg(0,std::ios::end);//跳转读写位置到文件末尾
size_t fsize=ifs.tellg();//获取末尾偏移量(也就是获取文件大小)
ifs.seekg(0,std::ios::beg);//跳转会起始
std::string body;
body.resize(fsize);
ifs.read(&body[0],fsize);//读取文件数据到body中
//以LZIP的方式压缩文件数据
std::string packed=bundle::pack(bundle::LZIP,body);
std::ofstream ofs;
ofs.open(ofilename,std::ios::binary);//打开压缩包文件
ofs.write(&packed[0],packed.size());//将压缩后的数据写入压缩包文件
ifs.close();
ofs.clear();
return 0;
}
先打开原文件,读取源文件数据到body中,使用bundle::pack对body进行压缩,压缩后结果保存到packed,打开一个新文件,将packed中的数据写入到新文件,形成压缩文件
编译生成compress,将bundle.cpp压缩到bundle.cpp.lz
文件解压
#include<iostream>
#include<string>
#include<fstream>
#include"bundle.h"
int main(int argc,char* argv[])
{
if(argc<3)
{
std::cout<<"argv[1] 是压缩包名称\n";
std::cout<<"argv[2] 是解压后的文件名\n";
return -1;
}
std::string ifilename=argv[1];
std::string ofilename=argv[2];
std::ifstream ifs;
ifs.open(ifilename,std::ios::binary);
ifs.seekg(0,std::ios::end);
size_t fsize=ifs.tellg();
ifs.seekg(0,std::ios::beg);
std::string body;
body.resize(fsize);
ifs.read(&body[0],fsize);
ifs.close();
std::string unpacked=bundle::unpack(body);
std::ofstream ofs;
ofs.open(ofilename,std::ios::binary);
ofs.write(&unpacked[0],unpacked.size());
ofs.close();
return 0;
}
先打开压缩包文件,将压缩包文件数据读取到body,使用bundle::unpask对body进行解压缩,解压结果保存到unpacked,打开一个新文件将unpacked中数据写入新文件,形成解压后文件
编译生成uncompress,将先前压缩的bundle.cpp.lz解压到bundle2.cpp
对比bundle.cpp和bundle2.cpp的MD5值相等
bundle.cpp生成静态库
bundle.cpp文件太大每次编译时间长,可以考虑将bundle.cpp生成静态库
g++ -c bundle.cpp bundle.o
ar -cr libbundle.a bundle.o
将libbundle.a添加到lib目录,编译时添加选项-L./lib -lbundle
httplib库
httplib 库,一个C++11 单文件头的跨平台HTTP/HTTPS 库。安装起来非常容易。只需包含httplib.h 在你的代码中即可。
httplib 库实际上是用于搭建一个简单的http 服务器或者客户端的库,这种第三方网络库,可以让我们免去搭建服务器或客户端的时间,把更多的精力投入到具体的业务处理中,提高开发效率。
httplib库Request类
http请求格式:
首行:[方法]+[url]+[版本]
Header:请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个
Content-Length属性来标识Body的长度;
Request类的功能
- 客户端保存所有http请求相关信息,最终组织http请求发送给服务器
- 服务器收到http请求后及进行解析,将解析的数据保存在request结构体中,等待后续处理
//文件信息结构体
struct MultipartFormData {
std::string name; //字段名称
std::string content; //文件内容
std::string filename; //文件名称
std::string content_type;//文件类型
};
using MultipartFormDataItems=std::vector<MultipartFormData>;
//Request结构体
struct Request {
std::string method;//请求方法
std::string path;//url
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;//获取对应的文件信息
};
httplib库Response类
http响应格式
首行: [版本号] + [状态码] + [状态码解释]
Header: 请求的属性, 冒号分割的键值对;每组属性之间使用\n分隔;遇到空行表示Header部分结束
Body: 空行后面的内容都是Body. Body允许为空字符串. 如果Body存在, 则在Header中会有一个Content-Length属性来标识Body的长度; 如果服务器返回了一个html页面, 那么html页面内容就是在body中.
Response类的功能
- 用户将响应数据放到结构体中,httplib会将其中的数据按照http响应格式组织成为http响应,发送给客户端。
struct Response {
std::string version; //协议版本号 http1.1
int status = -1; //响应状态码,
std::string reason;
Headers headers; //响应报头
std::string body; //响应给客户端的正文
std::string location; // 重定向位置
//以key-val将相应的字段设定到响应报头中
void set_header(const char *key, const char *val);//设置头部字段
void set_content(const std::string &s, const char *content_type);//设置正文
};
httplib中的Server类
Server类的功能
用于搭建http服务器
class Server {
//Handler一个函数指针,业务处理是涉及到的回调函数
using Handler = std::function<void(const Request &, Response &)>;
//Handlers是一个映射表,请求与处理函数映射表
using Handlers = std::vector<std::pair<std::regex, Handler>>;
std::function<TaskQueue *(void)> new_task_queue;//线程池,用于处理请求
//将Get/Post...方法的请求资源与处理函数设置到Handlers表中
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);
//搭建并启动服务器
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>>
;包含两个信息regex:正则表达式-用于匹配http请求资源路径
Handler:请求处理函数指针
可以理解为:**handlers是一张表,映射了一个客户端请求的资源路径和一个处理函数(用户自己定义的)**当服务器收到请求解析得到Request就会根据资源路径以及请求方法到handlers表中查找是否有对饮的处理函数,如果有则调用函数进行处理,没有返回404。handlers表决定哪个请求用哪个函数
ew_task_queue: 线程池,用于处理http请求,httplib收到一个新建连接,则将新的客户端连接抛入线程池中
线程池中线程的工作:
- 接受请求,解析请求,得到Request结构体也就是请求的数据
- 在handlers映射表中根据请求信息查找处理函数,调用函数进行处理
- 处理完毕,根据函数返回的Response结构体中的数据组织http响应并发送给客户端
httplib中的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请求提交多区域数据,用于多文件上传
}
httplib库搭建简单的服务器
#include"httplib.h"
void Hello(const httplib::Request& req, httplib::Response& rsp)
{
rsp.set_content("Hello world!!","text/plain");//设置正文
rsp.status=200;//设置响应状态码
}
void Numbers(const httplib::Request& req, httplib::Response& rsp)
{
auto num=req.matches[1];//0里边保存的是整体的path,往后的下标保存的是捕捉的数据
rsp.set_content(num,"text/plain");//设置正文
rsp.status=200;//设置响应状态码
}
void Multipart(const httplib::Request& req, httplib::Response& rsp)
{
auto ret=req.has_file("file");
if(ret==false)
{
std::cout<<"not file upload\n";
rsp.status=400;
return;
}
const auto& file=req.get_file_value("file");
rsp.body.clear();
rsp.body=file.filename;
rsp.body+='\n';
rsp.body+=file.content;
rsp.set_header("Content-Type","text/plain");
rsp.status=200;
return ;
}
int main()
{
httplib::Server server;//实例化server对象
server.Get("/hi",Hello);//注册一个针对/hi的Get请求的梳理函数Hello到Handlers映射表
server.Get(R"(/numbers/(\d+))",Numbers);
server.Post("multipart",Multipart);
server.listen("0.0.0.0",9090);
return 0;
}
实例化server对象,在Handler表结构中注册相应请求资源的处理函数,listen启动服务器
httplib库搭建简单的服务器
#include"httplib.h"
#define IP "43.143.228.57"
#define PORT 8080
using namespace std;
int main(){
//建立客户端对象
httplib::Client client(IP,PORT);
//单个文件信息组织
httplib::MultipartFormData file;
file.name="file"; //字段名
file.content="hello world"; //文件内容
file.filename="Hello.txt"; //文件名
file.content_type="text/plain"; //文件类型
//MultipartFormDataItems 文件信息数组
httplib::MultipartFormDataItems items;
items.push_back(file);
//请求服务器上/file资源,发送item文件集合给服务器
auto res=client.Post("/multipart",items);
cout<<res->status<<endl;
cout<<res->body<<endl;
return 0;
}