> 技术文档 > 序列化和反序列化(Linux)_服务器序列化反序列化

序列化和反序列化(Linux)_服务器序列化反序列化


1 序列化和反序列化

write和read实质是拷贝函数

1.1序列化和反序列化的概述

2网络版计算器

2.1代码实现

先把日志拷贝过来

2.1.1必须先要有网络功能

先把 TcpServer.hpp编写号

#pragma once#include #include \"Socket.hpp\"#include \"./logs/ljwlog.h\"class TcpServer{public: TcpServer() {} bool InitServer() {} void Start() {} ~TcpServer() {}private: uint16_t port;};

2.1.2 把套接字接口封装一下方便使用

#pragma once#include #include #include #include #include #include #include #include #include #include #include using namespace std;class Sock{public: Sock() {} ~Sock() {}public: void Socket()//创建套接字的接口 {} void bind()//绑定的接口 {} void Listen()//监听状态的接口 {} int Accept()//获取连接的接口 {} int Connect()//方便两个客户端和服务器都能使用这个Sock的这个公共方法 {}private: int sockfd_;};

2.1.3 TcpServer.hpp接口的补充

#pragma once#include #include \"Socket.hpp\"#include \"./logs/ljwlog.h\"class TcpServer{public: TcpServer() {} bool InitServer() { //先创建套接字,再绑定,设置监听状态 listensock_.Socket(); listensock_.bind(); listensock_.Listen(); } void Start() { while(true) {  int sockfd = listensock_.Accept(); } } ~TcpServer() {}private: uint16_t port_; Sock listensock_; //叫listen套接字,Listen完后有真正的网络文件描述符};

2.1.4 Sock.hpp接口的补充

#pragma once#include #include #include #include #include #include #include #include #include #include #include #include #include \"./logs/ljwlog.h\"using namespace std;enum{ SocketErr = 2, BindErr, ListenErr};const int backlog = 10;class Sock{public: Sock() {} ~Sock() {}public: void Socket()//创建套接字的接口 { sockfd_ = socket(AF_INET, SOCK_STREAM, 0);//流式套接字 第二个参数是协议类型 if(sockfd_ < 0) { FATAL(\"Socket errno,error:%d,errstring:%s\", errno, strerror(errno)); exit(SocketErr); } } void Bind(uint16_t port)//绑定的接口 { struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(port);//主机转网络 local.sin_addr.s_addr = INADDR_ANY;//ip默认0.0.0.0 if(bind(sockfd_, (struct sockaddr*)&local, sizeof(local)) < 0) { FATAL(\"Bind errno,error:%d,errstring:%s\", errno, strerror(errno)); exit(BindErr);  } } void Listen()//监听状态的接口 { if(listen(sockfd_, backlog) < 0) { FATAL(\"Listen errno,error:%d,errstring:%s\", errno, strerror(errno)); exit(ListenErr);  } } //  知道谁链接的我 int Accept(string *clientip, uint16_t *clientport)//获取连接的接口 { struct sockaddr_in peer;//远端的意思 socklen_t len = sizeof(peer); int newfd = accept(sockfd_, (struct sockaddr*)&peer, &len); if(newfd < 0) { WARN(\"accept error, %s: %d\", strerror(errno), errno); return -1; } //网络转主机 //拿出客户端的ip和端口号 char ipstr[64]; inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr));//网络转主机 *clientip = ipstr;//网络转主机 *clientport = ntohs(peer.sin_port);//网络转主机 return newfd; } void Close() { close(sockfd_); } int Connect()//方便两个客户端和服务器都能使用这个Sock的这个公共方法 { return 0; }private: int sockfd_;};

2.1.4 TcpServer.hpp提供服务

2.1.5定制协议(Protocol)(约定好)

双方约定好,把请求放在Request(请求),结果放在Response(响应)

约定好了,最好就不要动他了

#pragma once#includeclass Request{public:public: int x; int y; char op;// + - * / %};class Response{public:public: int result; int code;// 0,可信,否则!0具体是几,表明对应的错误原因};

2.1.6 (手写)序列化和反序列化

serialization 序列化(把struct 转换成 字符串)

deserialization 反序列化

2.1.6.1序列化

2.1.6.2反序列化

 2.1.6.3添加报头(要往网络里发 先添加报头)

这种

 

 2.1.6.4 提出有效载荷

2.1.6.5代码
#pragma once#include#includeusing namespace std;const string blank_space_sep = \" \";//空格分隔符const string protocol_sep = \"\\n\";//报文分隔符//序列化和反序列化解决的是对应的是一个报文内部的问题//还要解决一下报文与报文之间的问题//封装报头string Encode(string &content){ //封装报头 这里规定的协议就是长度+有效载荷 string package = to_string(content.size()); package += protocol_sep; package += content; package += protocol_sep; return package;}//\"len\"\\n\"x op y\"\\nstring Decode(string &package)//将\"x op y\"提取出来{ return \"\";}class Request{public: Request(int data1, int data2, char oper): x(data1), y(data2), op(oper) {}public: bool serialization(string *out) { //构建报文的有效载荷 //序列化的就是把结构化的(struct)转成string, // \"x op y\" //数字之间是不可能出现分隔符的 string s = to_string(x); s += blank_space_sep; s += op; s += blank_space_sep; s += to_string(y); *out = s; return true; } //\"x op y\" 进行分割 bool deserialization(const string &in) { size_t left = in.find(blank_space_sep); if(left == string::npos) return false;//没找到 string part_x = in.substr(0, left); size_t right = in.rfind(blank_space_sep);//逆着找 if(right == string::npos) return false;//没找到 string part_y = in.substr(right + 1); if(left + 2 != right) return false; op = in[right -1]; x = stoi(part_x); y = stoi(part_y); return true; }public: int x; int y; char op;// + - * / %};class Response{public: Response(int res, int c):result(res), code(c) {}public: //序列化是结构体转字符串的 bool serialization(string *out) { // 序列化成这样\"result code\" //构建报文的有效载荷 string s = to_string(result); s += blank_space_sep; s += to_string(code); *out = s; return true; } //反序列化 一定是你要传过来一个字符串 bool deserialization(const string &in) //\"result code\" 协议定制好了就必须是这个样子 { size_t pos = in.find(blank_space_sep); if(pos == string::npos) return false;//没找到 string part_left = in.substr(0, pos); string part_right = in.substr(pos + 1); result = stoi(part_left); code = stoi(part_right); return true; } public: int result; int code;// 0,可信,否则!0具体是几,表明对应的错误原因};
2.1.6.6 测试一下序列化和添加报头
#include#include \"TcpServer.hpp\"#include \"Protocol.hpp\"using namespace std;int main(){ Request req(123, 456, \'+\'); string s; req.serialization(&s);//序列化 cout << s << endl; s = Encode(s);//给s报文添加报头 cout << s ;//这是网络里发的样子 return 0;}

2.1.6.7 测试一下拿到报文的内容
#include#include \"TcpServer.hpp\"#include \"Protocol.hpp\"using namespace std;int main(){ Request req(123, 456, \'+\'); string s; req.serialization(&s);//序列化 //cout << s << endl; s = Encode(s);//给s报文添加报头 cout << s ;//这是网络里发的样子 //拿到报文的内容 string content; bool r = Decode(s, &content); cout<< content <<endl; return 0;}

2.1.6.8 测试对收到报文进行反序列化  打散成对象

2.1.6.9 测试Response的要往网络里发 先添加报头

2.1.7.0 测试解码

2.1.7.1 测试Response反序列化

2.1.7.2 创建ServerCal.hpp处理整个报文

如何处理整个报文

把协议和网络功能组合在一起 然后再把这个头文件\"ServerCal.hpp\"交给ServerCal.cc

#pragma once#include#include#include \"Protocol.hpp\"using namespace std;//如何处理整个报文//把协议和网络功能组合在一起 然后再把这个头文件\"ServerCal.hpp\"交给ServerCal.ccclass ServerCal{public: ServerCal() {} Response CalculatorHelper(const Request &req) { Response resp(0, 0); switch(req.op) { case \'+\': resp.result = req.x + req.y; case \'-\': resp.result = req.x - req.y; case \'*\': resp.result = req.x * req.y; case \'/\': resp.result = req.x / req.y; } return resp; } //提供一个网络计算服务 把先把报文给我 string Calculator(string &package)//肯定是收到一个序列化的请求报文 { //收到一个请求报文 添加报头 string content; bool r = Decode(package, &content); //报文不完整就直接返回 if(!r) return; //完整的报文 //请求的对象 Request req; //反序列化 r = req.deserialization(content); if(!r) return; //结果 Response resp = CalculatorHelper(req); //返回结果 //序列化 string s; resp.serialization(&s); //序列化后,想把它当作网络报文发出去 //添加报头 s = Encode(s); return s; } ~ServerCal() {}};
2.1.7.3 完善一下TcpServer.hpp

2.1.7.4 测试

2.1.7.5 完善一下ClientCal.cc

tcp客户端要绑定,但不用显示的绑定

客户端端口号是随机的,但是唯一的

udp端口那里随机绑定是首次发送数据的时候,那么你的端口号就随之确定了

tcp这里是面向连接的,客户端连接成功了,我才想让你进行通信

没有bind 也就没有listen了

向服务器发起连接的接口 connect

客户端发起connect的时候,系统进行自动随机bind

#include #include #include #include #include \"Socket.hpp\"#include \"Protocol.hpp\"using namespace std;void Usage(const string &proc){ cout << \"\\nUsage\" << proc << \" serverip serverport\\n\\n\" << endl;}//./clientcal serverip serverportint main(int argc, char *argv[]){ if (argc != 3) { Usage(argv[0]); exit(0); } string serverip = argv[1]; uint16_t serverport = stoi(argv[2]); Sock sockfd; sockfd.Socket(); bool r = sockfd.Connect(serverip, serverport); if (!r) return 1; srand(time(nullptr)); const string s = \"+-*/^!~\"; int cnt = 10; string inbuffer_stream; while (cnt--) { cout<< \"第\" <<cnt <<\"次测试...\" <<endl; int x = rand() % 100 + 1; usleep(1234); int y = rand() % 100 + 1; usleep(4321); char op = s[rand() % s.size()]; Request req(x, y, op); req.DebugPrint(); // 根据协议把请求发给服务器,Request是一个结构化的数据,发不了 // 先序列化得到对应的字符串 string package; req.serialization(&package); // 还是不能往网络里发送 添加报头还要 //\"len\"\\n\"x op y\"\\n package = Encode(package); cout << \"这是最新的发出去的请求:\\n\" << package; // 把请求往服务器端写 write(sockfd.Fd(), package.c_str(), package.size()); cout< 0) { buffer[n] = 0; inbuffer_stream += buffer;// \"len\"\\n\"result code\"\\n string content; //解码 bool r = Decode(inbuffer_stream, &content);//\"result code\" assert(r); //反序列化 Response resp; r = resp.deserialization(content); assert(r); resp.DebugPrint(); } sleep(1); } sockfd.Close(); return 0;}

2.1.7.6 客户端一次发多个请求呢

解决方案

2.1.7 (jsoncpp)序列化和反序列化

2.1.7.1 安装json库
sudo apt install libjsoncpp-dev

 这是头文件(只使用json.h)

2.1.7.2 简单的json的用法
#include#include \"jsoncpp/json/json.h\"using namespace std;int main(){ Json::Value root; root[\"x\"] = 100; root[\"y\"] = 100; root[\"op\"] = \'+\'; //描述 root[\"dect\"] = \"this is a + oper\"; //比较快的序列化 Json::FastWriter w; string res = w.write(root); cout<< res <<endl; return 0;}

编译时要带-ljsoncpp 

2.1.7.2.1 序列化 

2.1.7.2.2 反序列化 

2.1.7.2.3 json套json

2.1.7.3 Protocol.hpp中的json序列化和反序列化

2.1.7.4 守护进程化

3 重谈OSI七层模型

4 代码总合

4.1 ClientCal.cc

#include #include #include #include #include \"Socket.hpp\"#include \"Protocol.hpp\"using namespace std;void Usage(const string &proc){ cout << \"\\nUsage\" << proc << \" serverip serverport\\n\\n\" << endl;}//./clientcal serverip serverportint main(int argc, char *argv[]){ if (argc != 3) { Usage(argv[0]); exit(0); } string serverip = argv[1]; uint16_t serverport = stoi(argv[2]); Sock sockfd; sockfd.Socket(); bool r = sockfd.Connect(serverip, serverport); if (!r) return 1; srand(time(nullptr)); const string s = \"+-*/^!~\"; int cnt = 10; string inbuffer_stream; while (cnt--) { cout<< \"第\" <<cnt <<\"次测试...\" <<endl; int x = rand() % 100 + 1; usleep(1234); int y = rand() % 100 + 1; usleep(4321); char op = s[rand() % s.size()]; Request req(x, y, op); req.DebugPrint(); // 根据协议把请求发给服务器,Request是一个结构化的数据,发不了 // 先序列化得到对应的字符串 string package; req.serialization(&package); // 还是不能往网络里发送 添加报头还要 //\"len\"\\n\"x op y\"\\n package = Encode(package); cout << \"这是最新的发出去的请求:\\n\" << package; // 把请求往服务器端写 write(sockfd.Fd(), package.c_str(), package.size()); // write(sockfd.Fd(), package.c_str(), package.size()); // write(sockfd.Fd(), package.c_str(), package.size()); // write(sockfd.Fd(), package.c_str(), package.size()); // write(sockfd.Fd(), package.c_str(), package.size()); // write(sockfd.Fd(), package.c_str(), package.size()); cout< 0) { buffer[n] = 0; inbuffer_stream += buffer;// \"len\"\\n\"result code\"\\n cout<< inbuffer_stream <<endl; string content; //解码 bool r = Decode(inbuffer_stream, &content);//\"result code\" assert(r); //反序列化 Response resp; r = resp.deserialization(content); assert(r); resp.DebugPrint(); } sleep(1); } sockfd.Close(); return 0;}

4.2 makefile

.PHONY:allall:servercal clientcalservercal:ServerCal.ccg++ -g -o $@ $^ -lpthread -std=c++11 -ljsoncppclientcal:ClientCal.ccg++ -o $@ $^ -lpthread -std=c++11 -ljsoncpp.PHONT:cleanclean:rm -f clientcal servercal

4.3 Protocol.hpp

#pragma once#include#include#include \"jsoncpp/json/json.h\"using namespace std;const string blank_space_sep = \" \";//空格分隔符const string protocol_sep = \"\\n\";//报文分隔符//序列化和反序列化解决的是对应的是一个报文内部的问题//还要解决一下报文与报文之间的问题//添加报头 \"len\"\\n\"x op y\"\\nstring Encode(string &content){ //封装报头 这里规定的协议就是长度+有效载荷 string package = to_string(content.size()); package += protocol_sep; package += content; package += protocol_sep; return package;}//将\"x op y\"提取出来 有效载荷//\"len\"\\n\"x op y\"\\n package整个报文 *content把结果带出去bool Decode(string &package, string *content){ size_t pos = package.find(protocol_sep); if(pos == string::npos) return false; string len_str = package.substr(0, pos); size_t len = stoi(len_str); //拿到了len长度 //判断一下package的长度够不够 //package = len_str + content_str + 2 size_t total_len = len_str.size() + len + 2; if(package.size() < total_len) return false; //大于没事,因为肯定有完整的报文 //*content把结果带出去 \"x op y\" *content = package.substr(pos + 1, len); // earse 移除报文 package.erase(0, total_len); return true;}class Request{public: Request(int data1, int data2, char oper): x(data1), y(data2), op(oper) {} Request() {}public: bool serialization(string *out) { // //构建报文的有效载荷 // //序列化的就是把结构化的(struct)转成string, // // \"x op y\" // //数字之间是不可能出现分隔符的 // string s = to_string(x); // s += blank_space_sep; // s += op; // s += blank_space_sep; // s += to_string(y); // *out = s; // return true; //Json 序列化 Json::Value root; root[\"x\"] = x; root[\"y\"] = y; root[\"op\"] = op; Json::FastWriter w; *out = w.write(root); return true; } //\"x op y\" 进行分割 bool deserialization(const string &in) { // size_t left = in.find(blank_space_sep); // if(left == string::npos) return false;//没找到 // string part_x = in.substr(0, left); // size_t right = in.rfind(blank_space_sep);//逆着找 // if(right == string::npos) return false;//没找到 // string part_y = in.substr(right + 1); // if(left + 2 != right) return false; // op = in[right -1]; // x = stoi(part_x); // y = stoi(part_y); // return true; //Json 反序列化 Json::Value root; Json::Reader r; r.parse(in, root); //反序列化到root //提取出来 x = root[\"x\"].asInt(); y = root[\"y\"].asInt(); op = root[\"op\"].asInt(); return true; } void DebugPrint() { cout<< \"新请求构建完成\" << x << op << y << \"=?\" <<endl<<endl; }public: int x; int y; char op;// + - * / %};class Response{public: Response(int res, int c):result(res), code(c) {} Response() {}public: //序列化是结构体转字符串的 bool serialization(string *out) { // // 序列化成这样\"result code\" // //构建报文的有效载荷 // string s = to_string(result); // s += blank_space_sep; // s += to_string(code); // *out = s; // return true; //Json 序列化 Json::Value root; root[\"result\"] = result; root[\"code\"] = code; Json::FastWriter w; *out = w.write(root); return true; } //反序列化 一定是你要传过来一个字符串 bool deserialization(const string &in) //\"result code\" 协议定制好了就必须是这个样子 { // size_t pos = in.find(blank_space_sep); // if(pos == string::npos) return false;//没找到 // string part_left = in.substr(0, pos); // string part_right = in.substr(pos + 1); // result = stoi(part_left); // code = stoi(part_right); // return true; //Json 反序列化 Json::Value root; Json::Reader r; r.parse(in, root); //反序列化到root //提取出来 result = root[\"result\"].asInt(); code = root[\"code\"].asInt(); return true; } void DebugPrint() { cout<< \"响应结果完成\" << \"result:\"<< result <<\"code:\" <<code <<endl <<endl; }public: int result; int code;// 0,可信,否则!0具体是几,表明对应的错误原因};

4.4 ServerCal.cc

#include#include#include \"TcpServer.hpp\"#include \"Protocol.hpp\"#include \"ServerCal.hpp\"using namespace std;void Usage(const string& proc){ cout<<\"\\nUsage\" << proc << \"port\\n\\n\" <InitServer(); daemon(0, 0); tsvp->Start(); // Response resp(1000, 0); // //发回去 // string content; // resp.serialization(&content); // //cout << content << endl; // //要往网络里发 先添加报头 // string package = Encode(content); // cout << package << endl; // //解码 放进s // string s; // bool r = Decode(package, &s); // cout<< s <<endl; // //反序列化 打散成对象 // Response temp; // temp.deserialization(s); // cout<<\"\\n\\n\"; // cout<< temp.result <<endl; // cout<< temp.code << endl; // Request req(123, 456, \'+\'); // string s; // req.serialization(&s);//序列化 // //cout << s << endl; // s = Encode(s);//给s报文添加报头 // cout << s ;//这是网络里发的样子 // //拿到报文的内容 // string content; // bool r = Decode(s, &content); // cout<< content <<endl; // //对收到报文进行反序列化 打散成对象 // Request temp; // temp.deserialization(content); // cout<< temp.x <<endl << temp.op <<endl << temp.y <<endl; return 0;}

4.5 ServerCal.hpp

#pragma once#include#include#include \"Protocol.hpp\"using namespace std;//如何处理整个报文//把协议和网络功能组合在一起 然后再把这个头文件\"ServerCal.hpp\"交给ServerCal.ccclass ServerCal{public: ServerCal() {} Response CalculatorHelper(const Request &req) { Response 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; else  {  resp.result = req.x / req.y; } break; default: resp.code = 2; break; } return resp; } //提供一个网络计算服务 把先把报文给我 //  package: \"len\"\\n\"10 + 20\"\\n string Calculator(string &package)//肯定是收到一个序列化的请求报文 { //收到一个请求报文 添加报头 string content; bool r = Decode(package, &content); //报文不完整就直接返回 if(!r) return \"\"; //完整的报文 //请求的对象 Request req; //反序列化 r = req.deserialization(content); if(!r) return \"\"; //结果 Response resp = CalculatorHelper(req); //返回结果 //序列化 string s; resp.serialization(&s); //序列化后,想把它当作网络报文发出去 //添加报头 s = Encode(s); return s; } ~ServerCal() {}};

4.6 Socket.hpp

#pragma once#include #include #include #include #include #include #include #include #include #include #include #include #include \"./logs/ljwlog.h\"using namespace std;enum{ SocketErr = 2, BindErr, ListenErr};const int backlog = 10;class Sock{public: Sock() { } ~Sock() { }public: void Socket() // 创建套接字的接口 { sockfd_ = socket(AF_INET, SOCK_STREAM, 0); // 流式套接字 第二个参数是协议类型 if (sockfd_ < 0) { FATAL(\"Socket errno,error:%d,errstring:%s\", errno, strerror(errno)); exit(SocketErr); } } void Bind(uint16_t port) // 绑定的接口 { struct sockaddr_in local; memset(&local, 0, sizeof(local)); local.sin_family = AF_INET; local.sin_port = htons(port); // 主机转网络 local.sin_addr.s_addr = INADDR_ANY; // ip默认0.0.0.0 if (bind(sockfd_, (struct sockaddr *)&local, sizeof(local)) < 0) { FATAL(\"Bind errno,error:%d,errstring:%s\", errno, strerror(errno)); exit(BindErr); } } void Listen() // 监听状态的接口 { if (listen(sockfd_, backlog) < 0) { FATAL(\"Listen errno,error:%d,errstring:%s\", errno, strerror(errno)); exit(ListenErr); } } //  知道谁链接的我 int Accept(string *clientip, uint16_t *clientport) // 获取连接的接口 { struct sockaddr_in peer; // 远端的意思 socklen_t len = sizeof(peer); int newfd = accept(sockfd_, (struct sockaddr *)&peer, &len); if (newfd < 0) { WARN(\"accept error, %s: %d\", strerror(errno), errno); return -1; } // 网络转主机 // 拿出客户端的ip和端口号 char ipstr[64]; inet_ntop(AF_INET, &peer.sin_addr, ipstr, sizeof(ipstr)); // 网络转主机 *clientip = ipstr;  // 网络转主机 *clientport = ntohs(peer.sin_port); // 网络转主机 return newfd; } void Close() { close(sockfd_); } int Connect(const string &ip, const uint16_t &port) // 方便两个客户端和服务器都能使用这个Sock的这个公共方法 { struct sockaddr_in peer; memset(&peer, 0, sizeof(peer)); peer.sin_family = AF_INET; peer.sin_port = htons(port); inet_pton(AF_INET, ip.c_str(), &(peer.sin_addr)); int n = connect(sockfd_, (struct sockaddr *)&peer, sizeof(peer)); if (n == -1) { cerr << \"connect to\" << ip << \"::\" << port <<\"error\"<< endl; return false; } return true; } int Fd() { return sockfd_; }private: int sockfd_;};

4.7 TcpServer.hpp

#pragma once#include #include #include #include \"Socket.hpp\"#include \"./logs/ljwlog.h\"//  返回值 参数using fun_t = function;class TcpServer{public: TcpServer(uint16_t port, fun_t callback):port_(port), callback_(callback) {} bool InitServer( ) { //先创建套接字,再绑定,设置监听状态 listensock_.Socket(); listensock_.Bind(port_); listensock_.Listen(); INFO(\"init server\"); return true; } void Start() { signal(SIGCHLD, SIG_IGN);//忽略就不用等待了 signal(SIGPIPE, SIG_IGN);//忽略就不用等待了 while(true) {  string clientip; uint16_t clientport; int sockfd = listensock_.Accept(&clientip, &clientport); if(sockfd  0)  { buffer[n] = 0; inbuffer_stream += buffer; DEBUG(\"inbuffer_stream: %s\", inbuffer_stream.c_str()); while(true) { string info = callback_(inbuffer_stream); //如果没有读到完整的报文,就会返回一个空串infp if(info.empty()) break; write(sockfd, info.c_str(), info.size()); }  }  else if(n == 0) break;  else break; } exit(0); } close(sockfd);//不用等了直接关就好了 } } ~TcpServer() {}private: uint16_t port_; Sock listensock_; //叫listen套接字,Listen完后有真正的网络文件描述符 fun_t callback_;};