目录
序列化反序列化的概念
为什么要进行序列化和反序列化?
自定义协议实现业务
jsoncpp实现序列化和反序列化
序列化:
反序列化:
自定义协议+jsoncpp实现简易计算器服务整体逻辑
Server.cc
Client.cc
运行结果
序列化反序列化的概念
序列化就是把结构化的数据(比如一个结构体内部的数据)转换成字符串,方便在网络中进行传输,将应用和网络进行了解耦。
反序列化就是把序列化之后的字符串重新解析成结构化的数据,方便上层使用直接使用。
为什么要进行序列化和反序列化?
1.为了应用层网络通信的方便。我们发送结构化数据的时候,如果不进行序列化反序列化,是不便于网络传输的;将数据序列化为字符串以后,便于在网络中进行传输。(一个结构体要是硬要在网络中传输,那就要一个成员一个成员的以字符串的方式传输,非常麻烦)
2.为了方便上层使用结构体内部的成员,上层不需要对字符串进行解析,只需要直接使用下层反序列化得到的结构化数据即可。将应用和网络进行了解耦。
自定义协议实现业务
什么是协议?
双方约定好的结构化的数据,本质就是协议的表现。(相当于我们约定好,哪个字段是昵称,哪个是头像,哪个是消息等等)
目标:实现一个简易的计算器服务,能处理client发送过来的加减乘除等基本计算请求。
约定协议:client每次向server发送的请求结构包含两个操作数和一个操作符的字符串;而server每次给client的请求包含一个code表示状态码,一个result表示计算结果。
因此,我们可以在自定义协议文件中定义如下两个结构体,这就是我们自己定义的协议,server和client都必须遵守。
// 定义的协议,client && server 都必须遵守,这就叫做自定义协议
//请求格式
typedef struct request
{
int x;
int y;
char op; //"+-*/%"
} request_t;
// 响应格式
typedef struct response
{
int code; // server运算完毕的计算状态: code(0:success), code(-1: div 0) ...
int result; // 保存计算结果,先根据code判断是否计算成功,如果成功就查看result获取计算结果
}response_t;
jsoncpp实现序列化和反序列化
如果我们自己实现序列化和反序列化,会比较麻烦,因此可以借助一些工具,比如jsoncpp,帮助我们将结构化的数据序列化成字符串
jsoncpp这个工具是c++里面常用的序列化和反序列化的工具。需要使用这个命令安装:sudo yum install -y jsoncpp-devel。这个安装的本质就是将下面这些头文件和库文件放到了对应的目录下
[zebra@VM-8-12-centos cpp]$ ls /usr/include/jsoncpp/json
assertions.h autolink.h config.h features.h forwards.h json.h reader.h value.h version.h writer.h
[zebra@VM-8-12-centos cpp]$ ls /usr/lib64/libjson*
/usr/lib64/libjsoncpp.so /usr/lib64/libjsoncpp.so.0.10.5 /usr/lib64/libjson-c.so.2.0.1 /usr/lib64/libjson.so.0.1.0
/usr/lib64/libjsoncpp.so.0 /usr/lib64/libjson-c.so.2 /usr/lib64/libjson.so.0
序列化:
1.引入头文件#include <jsoncpp/json/json.h>,在编译运行的时候要加上-ljsoncpp这个库
2.有一个request的结构体request_t req = {10, 20, '*'};
3.将结构体中的数据放到一个Json::Value类型的对象里面,这个对象可以装载任意类型的值(json是一种kv式的序列化方案) datax,datay,operator这些键都是自己取的名字
Json::Value root; //可以承装任何对象, json是一种kv式的序列化方案
root["datax"] = req.x;
root["datay"] = req.y;
root["operator"] = req.op;
4.定义一个Json::FastWriter对象,然后调用里面的write方法,把Json::value对象转换成一个字符串,这样就可以进行传输啦。
这里的Writer有FastWriter和StyledWriter两种
Json::FastWriter writer;
std::string json_string = writer.write(root);
反序列化:
1.有一个序列化后的string字符串(R的作用就是指明””里面的内容是一个字符串,否则要进行很多次转义)
std::string json_string = R"({"datax":10,"datay":20,"operator":42})";
2.定义一个Json::Reader对象,再定义一个Json::Value对象,然后调用parse方法,就可以把序列化后的字符串转换成一个Json::Value对象。
reader.parse(json_string, root);
3.定义一个request结构体,然后从Json::Value对象里面把不同键对应的值取出来即可。(注意要调用as***方法指定对应值的类型),这里没有asChar,所以先转成int,然后强转成char
request_t req;
req.x = root["datax"].asInt();
req.y = root["datay"].asInt();
req.op = (char)root["operator"].asInt();
4.注意:反序列化完成,服务端拿到字符串数据以后要在字符串最后补上一个0,因为网络传输的时候并没有加上这个0,只有补上0,在C语言层面才可以把当前序列当做字符串看待)
/***
* @description: 请求序列化
* @param {request_t} &req
* @return {*}
*/
std::string SerializeRequest(const request_t &req)
{
Json::Value root; //可以承装任何对象, json是一种kv式的序列化方案
root["datax"] = req.x;
root["datay"] = req.y;
root["operator"] = req.op;
Json::FastWriter writer;
std::string json_string = writer.write(root);
return json_string;
}
/***
* @description: 请求反序列化
* @param {string} &json_string
* @param {request_t} &out,这里的out是一个输出型参数,用于存放反序列化后的响应结构体
* @return {*}
*/
void DeserializeRequest(const std::string &json_string, request_t &out)
{
Json::Reader reader;
Json::Value root; //对于parse方法来说,root属于输出型参数
reader.parse(json_string, root);
out.x = root["datax"].asInt();
out.y = root["datay"].asInt();
out.op = (char)root["operator"].asInt();
}
/***
* @description: 响应序列化
* @param {response_t} &resp
* @return {*}
*/
std::string SerializeResponse(const response_t &resp)
{
Json::Value root;
root["code"] = resp.code;
root["result"] = resp.result;
Json::FastWriter writer;
std::string res = writer.write(root);
return res;
}
/***
* @description: 响应反序列化
* @param {string} &json_string
* @param {response_t} &out,同上,out是一个输出型参数
* @return {*}
*/
void DeserializeResponse(const std::string &json_string, response_t &out)
{
Json::Reader reader;
Json::Value root;
reader.parse(json_string, root);
out.code = root["code"].asInt();
out.result = root["result"].asInt();
}
自定义协议+jsoncpp实现简易计算器服务整体逻辑
1.client根据我们的自定义协议构建好request请求,并使用jsoncpp将request请求进行序列化,然后发送给server
2.server接收到请求数据后,使用jsoncpp进行反序列化,根据自定义协议得到请求内容
3.server根据请求内容进行业务逻辑处理
4.server构建response响应,并使用jsoncpp进行序列化,发送给client。
5.client收到响应数据,使用jsoncpp反序列化,根据自定义协议得到响应结构response。
Server.cc
#include <pthread.h>
#include "My_protocol.hpp"
#include "Sock.hpp"
/**
* @description:使用手册,如果命令行参数不是2个就提示,因为要给出server启动时绑定的端口号
* @param {string} proc
* @return {*}
*/
static void Usage(string proc)
{
cout << "Usage: " << proc << " port" << endl;
exit(1);
}
void *HandlerRequest(void *args)
{
int sock = *(int *)args;
delete (int *)args;
pthread_detach(pthread_self()); //分离线程,这样就不需要wait,由OS帮我们回收
//1.读取请求
char buffer[1024];
request_t req; //构建请求结构体,后序作为输出型参数传入反序列化函数
ssize_t s = read(sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
buffer[s] = 0;
cout << "获取到一个新的请求: " << buffer << endl;
std::string str = buffer;
//2.反序列化请求
DeserializeRequest(str, req);
//3.业务逻辑处理
response_t resp = {0, 0};
switch (req.op)
{
case '+':
resp.result = req.x + req.y;
break;
case '-':
resp.result = req.x - req.y;
break;
case '*':
resp.result = req.x * req.y;
break;
case '/':
if (req.y == 0)
resp.code = -1; //代表除0
else
resp.result = req.x / req.y;
break;
case '%':
if (req.y == 0)
resp.code = -2; //代表模0
else
resp.result = req.x % req.y;
break;
default:
resp.code = -3; //代表请求方法异常
break;
}
cout << "request: " << req.x << req.op << req.y << endl;
//4.序列化响应并发送
std::string send_string = SerializeResponse(resp); //序列化之后的字符串
write(sock, send_string.c_str(),send_string.size());
cout << "服务结束: " << send_string << endl;
}
//5.关闭链接
close(sock);
}
//命令行输入: ./Server port
int main(int argc, char *argv[])
{
if (argc != 2)
Usage(argv[0]);
uint16_t port = atoi(argv[1]);
int listen_sock = Sock::Socket(); //获取监听套接字(已封装好)
Sock::Bind(listen_sock, port); //绑定端口(已封装好)
Sock::Listen(listen_sock); //开始监听(已封装好)
for (;;)
{
int sock = Sock::Accept(listen_sock); //获取连接
if (sock >= 0)
{
cout << "有新的连接来了" << endl;
int *pram = new int(sock);
pthread_t tid;
pthread_create(&tid, nullptr, HandlerRequest, pram); //创建一个线程来提供服务,执行业务逻辑
}
}
return 0;
}
Client.cc
#include "My_protocol.hpp"
#include "Sock.hpp"
/**
* @description: 使用手册,如果命令行参数不是3个就提示,因为要给出要访问的server服务的ip地址和端口号
* @param {string} proc
* @return {*}
*/
void Usage(string proc)
{
cout << "Usage: " << proc << " server_ip server_port" << endl;
}
//命令行输入: ./Client server_ip server_port
int main(int argc, char *argv[])
{
if (argc != 3)
{
Usage(argv[0]);
exit(1);
}
int sock = Sock::Socket();
Sock::Connect(sock, argv[1], atoi(argv[2])); //发起连接(已封装好)
//构建请求
request_t req;
memset(&req, 0, sizeof(req));
cout << "输入第一个操作数: ";
cin >> req.x;
cout << "输入第二个操作数: ";
cin >> req.y;
cout << "输入操作符: ";
cin >> req.op;
//序列化请求并发送
std::string json_string = SerializeRequest(req);
ssize_t s = write(sock, json_string.c_str(), json_string.size());
//接收响应并反序列化响应
char buffer[1024];
s = read(sock, buffer, sizeof(buffer) - 1);
if (s > 0)
{
response_t resp;
buffer[s] = 0;
std::string str = buffer;
DeserializeResponse(str, resp);
cout << "code[0:success]: " << resp.code << endl;
cout << "result: " << resp.result << std::endl;
}
return 0;
}
运行结果
client
server