数组、 布尔值和 null
发布时间:2025-06-24 18:48:57 作者:北方职教升学中心 阅读量:266
- Request:两个数字和一个运算符
- Response:结果数字 , 错误码 ,退出信息
他们是作为结构化的数据进行传输,那么想要进行传输就来到了最重要的部分序列化与反序列化!序列化与反序列化可以使用第三方库也可以自己进行编写。这里我们先使用第三方的Json库进行实现:
Jsoncpp 是一个用于处理 JSON 数据的 C++ 库。再调用回调函数对数据进行操作!得到结果之后就可以进行序列化,加入报头,再发送给客户端!
应用层的操作逻辑,Service并不关心,只要回调函数可以传回需要的结构体就可以! classService{public:Service(process_t process):_process(process){}voidIOExecute(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: %sn",addr.AddrStr().c_str());break;}// 此时获取到客户端发送的数据// 但是不能保证是否是完整的报文// 2.报文解析std::string str =Decode(message);// 通过去报头获取报文if(str.empty())continue;// 说明没有完整的报文!// 到这里说明有完整的报文!!!autoreq =Factory::BuildRequestDefault();// 3.反序列化初始化Requestreq->Deserialize(str);autores =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"classNetCal{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;}returnres;}~NetCal(){}};
逻辑很简单不在多加赘述!
5 总结
现在我们的程序分为了三层结构:
我们做到了最大程度的解耦!
- 传输层只负责获取链接,我们应用层要进行什么工作,只要是进行网络通信传输层的工作就是唯一的!
- 会话层进行IO操作!只要传输层提供了链接,会话层就可以获取数据,然后根据具体的协议进行数据的解析工作。 Jsoncpp 是开源的, 广泛用于各种需要处理 JSON 数据的 C++ 项目中:
- 简单易用: Jsoncpp 提供了直观的 API, 使得处理 JSON 数据变得简单。 字符串、并通过传输层的底层理解了为什么read系列函数时全双工支持同时读写的:TCP传输层有两个缓冲区,分别接收和发送。
从零开始掌握序列化
- 1 知识回顾
- 2 序列化与编写协议
- 2.1 使用Json进行序列化
- 2.2 编写协议
- 3 封装IOService
- 4 应用层 --- 网络计算器
- 5 总结
1 知识回顾
上一篇文章我们讲解了协议的本质是双方能够看到的结构化数据。
这样的结构逻辑十分清晰,并且解耦的非常优雅,值得反复品味!!!
在Linux中使用需要进行安装对应的JSON库:
ubuntu:sudo apt-get install libjsoncpp-devCentos: 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结构!
boolparsingSuccessful =reader.parse(json_string,root);// 访问 JSON 数据 std::string name =root["name"].asString();intage =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
进行序列化生成字符串,也要能够通过字符串反序列化得到三个变量
#pragmaonce#include<jsoncpp/json/json.h>#include<string>// 协议就是双方都认识的结构化数据// "len"\r\n"{json}"\r\nconststd::string sep ="\r\n";structRequest{public:Request(){}Request(intx,inty,charoper):_x(x),_y(y),_oper(oper){}~Request(){}boolSerialize(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;returntrue;}boolDeserialize(std::string &in){Json::Value root;// 创建json对象Json::Reader reader;// 读取boolres =reader.parse(in,root);if(res ==false)returnfalse;_x =root["x"].asInt();_y =root["y"].asInt();_oper =root["oper"].asInt();returntrue;}intX(){return_x;}intY(){return_y;}charOper(){return_oper;}private:int_x;int_y;char_oper;};structResponse{Response(){}Response(intres,intcode,std::string desc):_res(res),_code(code),_desc(desc){}~Response(){}boolSerialize(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;returntrue;}boolDeserialize(std::string &in){Json::Value root;// 创建json对象Json::Reader reader;// 读取boolres =reader.parse(in,root);if(res ==false)returnfalse;_res =root["res"].asInt();_code =root["code"].asInt();_desc =root["desc"].asInt();returntrue;}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\nconststd::string sep ="\r\n";// 加入报头std::string Encode(conststd::string &jsonstr){intlen =jsonstr.size();std::string lenstr =std::to_string(len);returnlenstr +sep +jsonstr +sep;}std::string Decode(std::string &packagestream){autopos =packagestream.find(sep);if(pos ==std::string::npos)returnstd::string();// 获取到lenstd::string lenstr =packagestream.substr(0,pos);intlen =std::stoi(lenstr);//算上报头的完整长度!inttotal =lenstr.size()+len +2*sep.size();if(total >packagestream.size())returnstd::string();// 到这里说明可以读取完整数据std::string jsonstr =packagestream.substr(pos +sep.size(),len);packagestream.erase(total);returnjsonstr;}
经过这样的操作,可以保证:
- 上层想要发送数据时,可以将数据包装为json字符串,并加入报头形成完整报文!
- 上层获取数据进行反序列化时可以获取到完整的json字符串!并成功解析为数据
3 封装IOService
将来我们的线程会执行将会执行这个回调函数方法,现在我们不再需要TcpServer来进行IO操作,TcpServer只负责进行获取链接,获取到连接后通过ThreadData结构体将数据传到线程中的回调函数中:
classThreadData{public:SockSPtr _sockfd;InetAddr _addr;TcpServer *_this;public:ThreadData(SockSPtr sockfd,InetAddr addr,TcpServer *p):_sockfd(sockfd),_this(p),_addr(addr){}};
在回调函数Execute中:
// 注意设置为静态函数 , 不然参数默认会有TcpServer* this!!!staticvoid*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();deletetd;returnnullptr;}
就可以解析出来套接字文件描述符和客户端信息了!解析出信息之后就去执行会话层的回调函数进行IO操作:
- Service内部只有一个成员变量,就是应用层的回调函数,Service解析出来数据之后就可以传入到应用层中进行使用
- IO中主要需要进行从sockfd文件中获取数据,然后通过协议进行解析,获取到真正的数据。 数字、该层只负责数据的解析,数据的处理交给应用层进行!
- 应用层Process:应用层是具有的业务逻辑,根据会话层解析出的数据,进行数据处理!
这样是一个非常非常优雅的封装操作!!!
2 序列化与编写协议
2.1 使用Json进行序列化
协议是IO的基础,只有协议确定下来,才可以进行通信。
我们这里想要实现一个网络计算器的应用,所以协议分为了两个类:Request和Response。最重要的是我们将TCP通信的代码进行的重构:
- 我们将Socket通信单独封装为一个类,负责Socket套接字的创建,bind绑定服务器端口号,进入监听模式…工作,基类Socket并不进行定义,只进行声明!具体实现由派生类TcpServer和UdpServer来进行
- TcpServer继承Socket类的所有方法,然后进行具体的函数定义!
- 上层的TcpServer直接底层使用TcpSocket对象就可以完成Socket系列操作,十分方便!
接下来我们要实现是这样的一个结构:
通信过程整体分为三层
- 传输层TcpServer:负责从Socket文件中获取链接,传输层不需要进行IO,获取到连接就让会话层通过连接获取数据!
- 会话层Service:根据传输层给的连接,从Sockfd文件中读取数据,解析出报文结构中的数据字符串,然后通过协议分离出结构化数据。