ToB企服应用市场:ToB评测及商务社交产业平台
标题:
【计网】从零开始掌握序列化 --- JSON实现协议 + 设计 传输\会话\应用 三层
[打印本页]
作者:
圆咕噜咕噜
时间:
2024-9-24 06:32
标题:
【计网】从零开始掌握序列化 --- JSON实现协议 + 设计 传输\会话\应用 三层
唯有空想才配让你不安,
唯有行动才能解除你的不安。
--- 卢思浩 ---
1 知识回首
上一篇文章我们讲解了协议的本质是两边能够看到的结构化数据。并通过传输层的底层明白了为什么read系列函数时全双工支持同时读写的:TCP传输层有两个缓冲区,分别吸取和发送。最重要的是我们将TCP通信的代码举行的重构:
我们将Socket通信单独封装为一个类,负责Socket套接字的创建,bind绑定服务器端口号,进入监听模式…工作,基类Socket并不举行界说,只举行声明!详细实现由派生类TcpServer和UdpServer来举行
TcpServer继承Socket类的全部方法,然后举行详细的函数界说!
上层的TcpServer直接底层使用TcpSocket对象就可以完成Socket系列使用,非常方便!
接下来我们要实现是如许的一个结构:
通信过程整体分为三层
传输层TcpServer:负责从Socket文件中获取链接,传输层不必要举行IO,获取到连接就让会话层通过连接获取数据!
会话层Service:根据传输层给的连接,从Sockfd文件中读取数据,解析出报文结构中的数据字符串,然后通过协议分离出结构化数据。该层只负责数据的解析,数据的处理交给应用层举行!
应用层Process:应用层是具有的业务逻辑,根据会话层解析出的数据,举行数据处理!
如许是一个非常非常优雅的封装使用!!!
2 序列化与编写协议
2.1 使用Json举行序列化
协议是IO的底子,只有协议确定下来,才可以举行通信。
我们这里想要实现一个网络计算器的应用,所以协议分为了两个类:Request和Response。分别作为传入的数据和传出的数据:
Request:两个数字和一个运算符
Response:结果数字 , 错误码 ,退出信息
他们是作为结构化的数据举行传输,那么想要举行传输就来到了最重要的部分序列化与反序列化!序列化与反序列化可以使用第三方库也可以自己举行编写。这里我们先使用第三方的Json库举行实现:
Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。 它提供了将 JSON 数据序列化为字符串以及从字符串反序列化为 C++ 数据结构的功能。 Jsoncpp 是开源的, 广泛用于各种必要处理 JSON 数据的 C++ 项目中:
简朴易用: Jsoncpp 提供了直观的 API, 使得处理 JSON 数据变得简朴。
高性能: Jsoncpp 的性能颠末优化, 能够高效地处理大量 JSON 数据。
全面支持: 支持 JSON 尺度中的全部数据类型, 包括对象、 数组、 字符串、 数字、 布尔值和 null。
错误处理: 在解析 JSON 数据时, Jsoncpp 提供了详细的错误信息和位置, 方便开辟者调试
在Linux中使用必要举行安装对应的JSON库:
ubuntu:sudo apt-get install libjsoncpp-dev
Centos: sudo yum install jsoncpp-devel
复制代码
安装之后就可以进使用用了:
使用起来是非常方便的:
Json::Value是最重要的类,这是对Json数据结构进程使用和表现的关键类
创建好类Json::Value之后就可以通过[ ]使用root["x"] = _x;,像如许就可以举行赋值
将Json数据结构转换为字符串依赖 Json::FastWriter 或 Json::StreamWriter都可以转换成字符串
Json::StyledWriter writer;
std::string s = writer.write(root)
复制代码
通过Json::Reader可以快速将字符串反序列化得到Json结构!
bool parsingSuccessful = reader.parse(json_string,root);
// 访问 JSON 数据
std::string name = root["name"].asString();
int age = root["age"].asInt(); std::string city =
root["city"].asString();
复制代码
通过如许就就可以简洁的完成序列化与反序列化的工作!
2.2 编写协议
根据我们的需求在参加Json使用我们就可以把协议写出来,代码虽然很长但是很好明白:
Request类中必要根据 int x , int y , char oper举行序列化天生字符串,也要能够通过字符串反序列化得到三个变量
Response类中必要根据 int res , int code , std::string desc举行序列化天生字符串,也要能够通过字符串反序列化得到三个变量
#pragma once
#include <jsoncpp/json/json.h>
#include <string>
// 协议就是双方都认识的结构化数据
// "len"\r\n"{json}"\r\n
const std::string sep = "\r\n";
struct Request
{
public:
Request() {}
Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper)
{
}
~Request()
{
}
bool Serialize(std::string *out)
{
// 使用现成的 Json 库
Json::Value root;
root["x"] = _x;
root["y"] = _y;
root["oper"] = _oper;
Json::FastWriter writer;
std::string s = writer.write(root);
*out = s;
return true;
}
bool Deserialize(std::string &in)
{
Json::Value root; // 创建json对象
Json::Reader reader; // 读取
bool res = reader.parse(in, root);
if (res == false)
return false;
_x = root["x"].asInt();
_y = root["y"].asInt();
_oper = root["oper"].asInt();
return true;
}
int X() { return _x; }
int Y() { return _y; }
char Oper() { return _oper; }
private:
int _x;
int _y;
char _oper;
};
struct Response
{
Response() {}
Response(int res, int code, std::string desc) : _res(res), _code(code), _desc(desc)
{
}
~Response()
{
}
bool Serialize(std::string *out)
{
// 使用现成的 Json 库
Json::Value root;
root["res"] = _res;
root["code"] = _code;
root["desc"] = _desc;
Json::FastWriter writer;
std::string s = writer.write(root);
*out = s;
return true;
}
bool Deserialize(std::string &in)
{
Json::Value root; // 创建json对象
Json::Reader reader; // 读取
bool res = reader.parse(in, root);
if (res == false)
return false;
_res = root["res"].asInt();
_code = root["code"].asInt();
_desc = root["desc"].asInt();
return true;
}
int _res;
int _code; // 退出码 0:success 1:div zero 2:非法操作
std::string _desc;
};
复制代码
看一下结果:
完成了底子的序列化和反序列化之后,我们就可以做到从sockfd流中读取数据了吗??不可以!由于不知道Json字符串的长度,就不知道应该读取多少字节!如允许就做不到正确的从数据中获取json字符串!
所以我们还有做一步特殊处理:
必要对天生的Json字符串参加报头len记载json字符串的长度,中间以sep分隔符分割!
必要对得到到的数据举行解析,去除报头得到一个Json字符串!
// "len"\r\n"{json}"\r\n
const std::string sep = "\r\n";
// 加入报头
std::string Encode(const std::string &jsonstr)
{
int len = jsonstr.size();
std::string lenstr = std::to_string(len);
return lenstr + sep + jsonstr + sep;
}
std::string Decode(std::string &packagestream)
{
auto pos = packagestream.find(sep);
if (pos == std::string::npos)
return std::string();
// 获取到len
std::string lenstr = packagestream.substr(0, pos);
int len = std::stoi(lenstr);
//算上报头的完整长度!
int total = lenstr.size() + len + 2 * sep.size();
if (total > packagestream.size())
return std::string();
// 到这里说明可以读取完整数据
std::string jsonstr = packagestream.substr(pos + sep.size(), len);
packagestream.erase(total);
return jsonstr;
}
复制代码
颠末如许的使用,可以包管:
上层想要发送数据时,可以将数据包装为json字符串,并参加报头形成完整报文!
上层获取数据举行反序列化时可以获取到完整的json字符串!并成功解析为数据
3 封装IOService
将来我们的线程会实行将会实行这个回调函数方法,如今我们不再必要TcpServer来举行IO使用,TcpServer只负责举行获取链接,获取到连接后通过ThreadData结构体将数据传到线程中的回调函数中:
class ThreadData
{
public:
SockSPtr _sockfd;
InetAddr _addr;
TcpServer *_this;
public:
ThreadData(SockSPtr sockfd, InetAddr addr, TcpServer *p) : _sockfd(sockfd),
_this(p),
_addr(addr)
{
}
};
复制代码
在回调函数Execute中:
// 注意设置为静态函数 , 不然参数默认会有TcpServer* this!!!
static void *Execute(void *args)
{
pthread_detach(pthread_self()); // 线程分离!!!
// 执行Service函数
TcpServer::ThreadData *td = static_cast<TcpServer::ThreadData *>(args);
td->_this->_service(td->_sockfd, td->_addr);
td->_sockfd->Close();
delete td;
return nullptr;
}
复制代码
就可以解析出来套接字文件描述符和客户端信息了!解析出信息之后就去实行会话层的回调函数举行IO使用:
Service内部只有一个成员变量,就是应用层的回调函数,Service解析出来数据之后就可以传入到应用层中进使用用
IO中主要必要举行从sockfd文件中获取数据,然后通过协议举行解析,获取到真正的数据。再调用回调函数对数据举行使用!得到结果之后就可以举行序列化,参加报头,再发送给客户端!
应用层的使用逻辑,Service并不关心,只要回调函数可以传回必要的结构体就可以!
class Service
{
public:
Service(process_t process) : _process(process)
{
}
void IOExecute(SockSPtr sock, InetAddr &addr)
{
LOG(INFO, "service start!!!\n");
std::string message;
while (true)
{
// 1. 进行读取
ssize_t n = sock->Recv(&message);
if (n < 0)
{
LOG(ERROR, "read error: %s\n", addr.AddrStr().c_str());
break;
}
// 此时获取到客户端发送的数据
// 但是不能保证是否是完整的报文
// 2.报文解析
std::string str = Decode(message); // 通过去报头获取报文
if (str.empty())
continue; // 说明没有完整的报文!
// 到这里说明有完整的报文!!!
auto req = Factory::BuildRequestDefault();
// 3.反序列化初始化Request
req->Deserialize(str);
auto res = Factory::BuildResponseDefault();
// 4.业务处理
res = _process(req);
// 5.进行序列化处理
std::string ret;
res->Serialize(&ret);
// 6.加入报头
Encode(ret);
// 7.将获取的数据发送回去
sock->Send(ret);
}
}
~Service()
{
}
private:
process_t _process;
};
复制代码
4 应用层 — 网络计算器
应用层根据详细必要可以随时改变,我这里以网络计算器为例子举行誊写:
#include "Protocol.hpp"
class NetCal
{
public:
NetCal() {}
std::shared_ptr<Response> Calculator(std::shared_ptr<Request> req)
{
std::shared_ptr<Response> res = Factory::BuildResponseDefault();
switch (req->Oper())
{
case '+':
res->_res = req->X() + req->Y();
res->_code = 0;
res->_desc = "success";
break;
case '-':
res->_res = req->X() - req->Y();
res->_code = 0;
res->_desc = "success";
break;
case '*':
res->_res = req->X() * req->Y();
res->_code = 0;
res->_desc = "success";
break;
case '/':
{
if (req->Y() == 0)
{
res->_code = 1;
res->_desc = "div zero";
}
res->_res = req->X() / req->Y();
res->_code = 0;
res->_desc = "success";
}
break;
case '%':
{
if (req->Y() == 0)
{
res->_code = 1;
res->_desc = "mod zero";
}
res->_res = req->X() % req->Y();
res->_code = 0;
res->_desc = "success";
}
break;
default:
res->_code = 2;
res->_desc = "illegal operations";
break;
}
return res;
}
~NetCal() {}
};
复制代码
逻辑很简朴不在多加赘述!
5 总结
如今我们的程序分为了三层结构:
我们做到了最大程度的解耦!
传输层只负责获取链接,我们应用层要举行什么工作,只要是举行网络通信传输层的工作就是唯一的!
会话层举行IO使用!只要传输层提供了链接,会话层就可以获取数据,然后根据详细的协议举行数据的解析工作。协议根据现实情况改变,但是会话层的工作逻辑是不变的!
应用层只管举行数据处理即可,什么但不不必要思量!完成工作后返回给会话层数据即可!
如许的结构逻辑非常清晰,而且解耦的非常优雅,值得反复咀嚼!!!
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
欢迎光临 ToB企服应用市场:ToB评测及商务社交产业平台 (https://dis.qidao123.com/)
Powered by Discuz! X3.4