序列化
序列化是指将数据结构或对象状态转换成可以存储或传输的格式的过程。在序列化过程中,对象的状态信息被转换为可以保持或传输的格式(如二进制、XML、JSON等)。序列化后的数据可以被写入到文件、数据库、内存缓冲区中,或者通过网络发送到另一个系统。
序列化的主要目的包括:
- 持久化:将对象状态保存到存储系统中,以便程序在重新启动时能够重新加载这些数据。
- 网络传输:在分布式系统中,对象需要通过网络发送到其他节点,序列化是实现这一点的关键步骤。
- 数据交换:不同的应用程序或系统之间可能需要交换数据,序列化提供了一种标准化的数据格式。
反序列化
反序列化是序列化的逆过程,即将序列化后的数据(如二进制、XML、JSON等)恢复成原始的数据结构或对象状态。在反序列化过程中,原始的数据格式被解析并重新构建成原始对象或数据结构。
反序列化的主要目的是:
- 恢复对象状态:从存储系统或网络接收的数据中恢复对象的状态。
- 数据使用:在应用程序中使用反序列化后的数据。
JSON
JSON(JavaScript Object Notation)是一种轻量级的数据交换格式,易于人阅读和编写,同时也易于机器解析和生成。它基于JavaScript的一个子集,但是JSON是独立于语言的,许多编程语言都有解析JSON的库,因此JSON成为了现代互联网中数据交换的标准格式之一。
Jsoncpp
Jsoncpp是一个开源的C++库,主要用于解析和生成JSON数据格式。
安装方式:
特点:
- 轻量级:Jsoncpp具有很小的代码体积和低的内存占用,非常适合嵌入式系统或资源受限的环境。
- 跨平台:Jsoncpp可以在多种操作系统上运行,包括Windows、Linux、Mac等。
- 简单易用:Jsoncpp提供了简洁的API,使得解析和生成JSON数据变得非常容易。开发人员可以通过简单的函数调用来实现JSON数据的读取、修改和生成。
- 高性能:Jsoncpp采用了高效的算法和数据结构,提供了快速的JSON解析和生成功能。
- 可靠稳定:Jsoncpp经过广泛的测试和使用,已经成为一个成熟的库,具有良好的稳定性和可靠性。
核心功能:
Jsoncpp的核心数据结构是Json::Value
类,它表示JSON数据的各种类型,包括对象、数组、字符串、数字等。通过Json::Value类及其相关方法,开发者可以直观地操作JSON数据,无需关注复杂的内部实现细节。
Jsoncpp还提供了几个重要的类来支持JSON数据的解析和生成,包括:
Json::Reader
:用于将JSON字符串或文件解析成Json::Value对象。
Json::Writer
:(及其子类如Json::FastWriter、Json::StyledWriter等):用于将Json::Value对象序列化为JSON字符串。
示例:网络计算器
实现一个服务器版的计算器,客户端将要要计算的两个数发送到服务端,服务端计算好将结果返回给客户端;
期间,我们会用一种结构体存储对应的计算的两个数以及符号;数据通过序列化发送到服务端,服务端需要将字符串反序列化解析得到数据;
Socket.hpp
#include <iostream>#include <string>#include <functional>#include <sys/types.h> /* See NOTES */#include <sys/socket.h>#include <netinet/in.h>#include <arpa/inet.h>#include <unistd.h>#include <cstring>#include <pthread.h>#include <sys/types.h>#include <memory>#include "InetAddr.hpp"#include "Log.hpp"namespace socket_ns{ class Socket; const static int gbacklog=8;//默认最大连接数 using socket_sptr=std::shared_ptr<Socket>;//套接字指针 enum { SOCKET_ERROR = 1, BIND_ERROR, LISTEN_ERROR, USAGE_ERROR }; //在基类创建一系列虚函数,只要派生类能用到就在这里创建 class Socket { public: virtual void CreateSocketOrDie() =0; //创建套接字 virtual void BindSocketOrDie(InetAddr& addr) =0; //绑定套接字 virtual void ListenSocketOrDie()=0; //监听套接字 virtual socket_sptr Accepter(InetAddr* addr) =0; //接受客户端 virtual bool Connector(InetAddr &addr) = 0; //连接客户端 virtual int SockFd() = 0; //获取Sockfd virtual int Recv(std::string *out) = 0; //接收对方信息 virtual int Send(const std::string &in) = 0; //发送给对方信息 public: //创建监听套接字,将一系列操作细分化,直接引用对应函数直接创建 void BuildListenSocket(InetAddr& addr) { CreateSocketOrDie(); BindSocketOrDie(addr); ListenSocketOrDie(); } bool BuildClientSocket(InetAddr &addr) { CreateSocketOrDie(); return Connector(addr); } }; class TcpSocket : public Socket { public: TcpSocket(int sockfd=-1) :_sockfd(sockfd) {} void CreateSocketOrDie() override //override明确的重写基类函数 { _sockfd=socket(AF_INET,SOCK_STREAM,0); if(_sockfd<0) { LOG(FATAL, "socket error"); exit(SOCKET_ERROR); } LOG(DEBUG, "socket create success, sockfd is : %d/n", _sockfd); } void BindSocketOrDie(InetAddr& addr) override { struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(addr.Port()); local.sin_addr.s_addr = inet_addr(addr.Ip().c_str()); int n=bind(_sockfd,(struct sockaddr*)&local,sizeof(local)); if (n < 0) { LOG(FATAL, "bind error"); exit(BIND_ERROR); } LOG(DEBUG, "bind success, sockfd is : %d/n", _sockfd); } void ListenSocketOrDie() override { int n=listen(_sockfd,gbacklog); if (n < 0) { LOG(FATAL, "listen error"); exit(LISTEN_ERROR); } LOG(DEBUG, "listen success, sockfd is : %d/n", _sockfd); } socket_sptr Accepter(InetAddr* addr) override { struct sockaddr_in peer; socklen_t len=sizeof(peer); int sockfd = accept(_sockfd,(struct sockaddr*)&peer,&len); if (sockfd < 0) { LOG(WARNING, "accept error/n"); return nullptr; } *addr=peer; socket_sptr sock=std::make_shared<TcpSocket>(sockfd); return sock; } virtual bool Connector(InetAddr& addr) { struct sockaddr_in server; memset(&server,0,sizeof(server)); server.sin_family=AF_INET; server.sin_addr.s_addr=inet_addr(addr.Ip().c_str()); server.sin_port=htons(addr.Port()); int n=connect(_sockfd,(struct sockaddr*)&server,sizeof(server)); if (n < 0) { std::cerr << "connect error" << std::endl; return false; } return true; } int Recv(std::string *out) override { char inbuffer[1024]; ssize_t n = recv(_sockfd,inbuffer,sizeof(inbuffer)-1,0); if (n > 0) { inbuffer[n] = 0; *out += inbuffer; // 接收次数可能不只一次,一般是多次的, } return n; } int Send(const std::string &in) override { int n = send(_sockfd,in.c_str(),in.size(),0); return n; } int SockFd() override { return _sockfd; } ~TcpSocket() {} private: int _sockfd; };}
ProToCol.hpp
主要是发出请求时将数据序列化和接收数据时反序列化;服务端响应时接收数据的反序列化和发送结果时序列化;
#pragma once #include <iostream>#include <string>#include<unistd.h>#include<memory>#include<jsoncpp/json/json.h>namespace protocol_ns{ // 协议的样子: // 报文 = 报头+有效载荷 // "有效载荷的长度"/r/n"有效载荷"/r/n const std::string SEP= "/r/n"; // 解决TCP的粘报问题,TCP 读取不全的问题 std::string Encode(const std::string &json_str) { int json_str_len = json_str.size(); //有效载荷的长度 std::string proto_str = std::to_string(json_str_len); //转为string proto_str += SEP; //+ 分隔符 proto_str += json_str;// + 数据字符串 proto_str += SEP;// + 分隔符 return proto_str; //返回一个报文 } //将报文分析出数据字符串出来 std::string Decode(std::string &inbuffer) { auto pos = inbuffer.find(SEP); //找到分隔符的位置 if (pos == std::string::npos) return std::string(); std::string len_str = inbuffer.substr(0, pos);//前头的有效数据长度的字符串 if (len_str.empty()) return std::string(); int packlen = std::stoi(len_str);//记录数据字符串的实际长度(传递时的差错主要出在这里) int total = packlen + len_str.size() + 2 * SEP.size(); //报文总长度 if (inbuffer.size() < total) return std::string(); std::string package = inbuffer.substr(pos + SEP.size(), packlen); //取出数据字符串 inbuffer.erase(0, total); //删除掉原先的报文 return package; } //请求将我们的数据序列化和反序列化(客户端) class Request { public: Request() { } Request(int x, int y, char oper) : _x(x), _y(y), _oper(oper) { } //序列化:将结构体数据转换为字符串 bool Serialize(std::string* out) { Json::Value root; //Json::Value: Json格式的值 root["x"] = _x; root["y"] = _y; root["oper"] = _oper; Json::FastWriter writer; *out=writer.write(root); //将Json值转换为字符串 return true; } //反序列化:将字符串转换为结构体数据 bool DeSerialize(const std::string& in) { Json::Value root; Json::Reader reader;//解析字符串 bool res=reader.parse(in,root);//将字符串转为Json值,存放于root中 if (!res) return false; //再将Json值转为结构体数据 _x = root["x"].asInt(); _y = root["y"].asInt(); _oper = root["oper"].asInt(); return true; } public: int _x; int _y; char _oper; //操作符 _x 加减乘除 _y }; //将结果序列化和反序列化(服务端) class Response { public: Response() { } Response(int result, int code) : _result(result), _code(code) { } bool Serialize(std::string *out) { // 转换成为字符串 Json::Value root; root["result"] = _result; root["code"] = _code; Json::FastWriter writer; // Json::StyledWriter writer; *out = writer.write(root); return true; } bool Deserialize(const std::string &in) { Json::Value root; Json::Reader reader; bool res = reader.parse(in, root); if (!res) return false; _result = root["result"].asInt(); _code = root["code"].asInt(); return true; } public: int _result; // 结果 int _code; // 0:success 1: 除0 2: 非法操作 3. 4. 5 }; //创建需求 class Factory { public: Factory() { srand(time(nullptr) ^ getpid()); opers = "+-*/%^&|"; } std::shared_ptr<Request> BuildRequest() { int x = rand() % 10 + 1; usleep(x * 10); int y = rand() % 5; // [0,1,2,3,4] usleep(y * x * 5); char oper = opers[rand() % opers.size()]; std::shared_ptr<Request> req= std::make_shared<Request>(x,y,oper); return req; } std::shared_ptr<Response> BuildResponse() { return std::make_shared<Response>(); } ~Factory() { } private: std::string opers; };}
CalCulate.hpp
将已经反序列化的数据通过计算得出结果:
#pragma once#include <iostream>#include "ProToCol.hpp"using namespace protocol_ns;class Calculate{public: Calculate() { } //根据输入的请求通过实际计算转换为结果 Response Excute(const Request &req) { Response resp(0, 0); switch (req._oper) { 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; } else { resp._result = req._x / req._y; } } break; case '%': { if (req._y == 0) { resp._code = 2; } else { resp._result = req._x % req._y; } } break; default: resp._code = 3; break; } return resp; } ~Calculate() { }private:};
TcpServerMain.cc
#include <iostream>#include <functional>#include <memory>#include "TcpServer.hpp"#include "ProToCol.hpp"#include"CalCulate.hpp"using namespace protocol_ns;using callback_t = std::function<Response(const Request&)>;//响应计算结果的函数指针void Usage(std::string proc){ std::cout << "Usage:/n/t" << proc << " local_port/n" << std::endl;}//由于接收的是报文,需要通过反序列化和序列化反复转换并且期间要完成计算的服务class Service{public: Service(callback_t cb) : _cb(cb) { } //套接字指针:客户端的sockfd 网络地址:客户端 void ServiceHelper(socket_sptr sockptr,InetAddr client) { int sockfd = sockptr->SockFd(); LOG(DEBUG, "get a new link, info %s:%d, fd : %d/n",client.Ip().c_str(),client.Port(),sockfd ); std::string clientaddr = "[" + client.Ip() + ":" + std::to_string(client.Port()) + "] "; std::string inbuffer; while(true) { sleep(5); Request req; // 1.接收来自客户端的发送数据,接收时是已被序列化的数据信息 int n = sockptr->Recv(&inbuffer); if (n < 0) { LOG(DEBUG, "client %s quit/n", clientaddr.c_str()); break; } std::string package; while (true)//传递途中可能会出现数据丢失,要通过循环直到找到有效正确的数据信息 { //2.分析报文中的有效数据 sleep(1); std::cout << "inbuffer: " << inbuffer << std::endl; package = Decode(inbuffer);//解析报文 if (package.empty()) break; std::cout << "------------------------begin---------------" << std::endl; std::cout << "resq string:/n"<< package << std::endl; // 3.反序列化,将字符串变为结构体信息 req.DeSerialize(package); // 4. 业务处理 Response resp = _cb(req); // 5. 对应答做序列化 std::string send_str; resp.Serialize(&send_str); std::cout << "resp Serialize:" << std::endl; std::cout << send_str << std::endl; // 6. 添加长度报头 send_str = Encode(send_str); std::cout << "resp Encode:" << std::endl; std::cout << send_str << std::endl; // "len"/r/n"{ }"/r/n" sockptr->Send(send_str); // 本次不对发送做处理, EPOLL } } }private: callback_t _cb; //将需求转换为结果};// ./tcpserver portint main(int argc, char *argv[]){ if (argc != 2) { Usage(argv[0]); return 1; } uint16_t port = std::stoi(argv[1]); Calculate cal; Service calservice(std::bind(&Calculate::Excute, &cal, std::placeholders::_1));//通过Service解决报文问题 io_service_t service = std::bind(&Service::ServiceHelper, &calservice, std::placeholders::_1, std::placeholders::_2);//将服务和服务器耦合 std::unique_ptr<TcpServer> tsvr = std::make_unique<TcpServer>(port, service); tsvr->Loop(); return 0;}
TcpClientMain.cc
#include <iostream>#include <string>#include <memory>#include <ctime>#include "Socket.hpp"#include "ProToCol.hpp"#include "InetAddr.hpp"using namespace socket_ns;using namespace protocol_ns;void Usage(std::string proc){ std::cout << "Usage:/n/t" << proc << " serverip serverport/n"<< std::endl; }int main(int argc,char* argv[]){ if (argc != 3) { Usage(argv[0]); exit(1); } std::string serverip = argv[1];//服务端ip uint16_t serverport = std::stoi(argv[2]);//服务端端口号 InetAddr serveraddr(serverip, serverport);//创建网络地址 Factory factory;// std::shared_ptr<Socket> cli=std::make_shared<TcpSocket>();//创建套接字的智能指针 bool res=cli->BuildClientSocket(serveraddr); //创建Client套接字并连接到服务端 std::string inbuffer; //接收应答时存储对方的数据 while (res) { sleep(1); std::string str; //一次创建5个请求出来 //for(int i=0;i<5;i++) //{ // 1. 构建一个请求 auto req = factory.BuildRequest(); // 2. 对请求进行序列化 std::string send_str; //序列化后保存于此 req->Serialize(&send_str); std::cout << "Serialize: /n"<< send_str << std::endl; // 3. 添加长度报头 send_str = Encode(send_str); std::cout << "Encode: /n"<< send_str << std::endl; str += send_str; //} //4.发送报文 cli->Send(str); // 5. 读取应答 int n = cli->Recv(&inbuffer);//接收应答 if (n <= 0) break; std::string package = Decode(inbuffer); //解析报文 if (package.empty()) //一旦为空那么继续解析下一个报文 continue; // 6. 反序列化 auto resp = factory.BuildResponse(); resp->Deserialize(package); std::cout<<"result: "<<resp->_result<<"["<< resp->_code <<"]"<<std::endl; }}
结果