网络 :序列和反序列化
网络 :序列和反序列化
- (一)序列和反序列 概念
- (二)实例
- 1. 封装socket 接口
- 2. 制定协议(用于实现序列和反序列化)
- 3. 计算(实现计算器功能)
- 4. 服务器(将上面所有的类功能调用起来)
- 5. 服务端
- 6.客户端
- (三) Json
- 1.使用例子
- 2.使用json实现网络计算器的序列和反序列
- 四) OSI7层模型
(一)序列和反序列 概念
序列化:
- 序列化是指将对象的状态信息转换为可以存储或传输的形式的过程。这种形式通常是字节流或其他结构化数据格式,便于在网络中传递或保存到持久化介质(如磁盘文件)。通过序列化,对象可以在不同的环境中被安全地传递或长期保存。
在实际应用中,序列化的核心目标是确保对象的完整性和可传递性。这意味着无论是在同一台机器上的不同进程之间还是跨网络的不同设备之间,都可以无损地传递对象的数据状态
反序列化:
- 反序列化则是序列化的逆操作,即将已序列化的数据重新还原为原始的对象实例。这一过程涉及解析字节流并依据其中保存的对象状态及描述信息重建对象。反序列化使得程序能够恢复先前保存的对象状态,从而继续执行后续逻辑或完成特定任务。
序列化和反序列化就是用户规定的一种约定 ,他是用户层上的 “约定” ,这个约定由程序员自己来规定,而通信双方都要遵守这种协议。
(二)实例
下面我们通过实现一个简易的 通信计算器 来更好的理解序列和反序列化。
1. 封装socket 接口
这里的通信时基于TCP协议。封装socket接口方便客户端和使用端更好的使用。
Sock.hpp
头文件封装套接字接口 ,Log.hpp
是一个日志系统。
#pragma Once
#include <iostream>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>#include "Log.hpp"Log lg;enum
{SOCK_ERR = 1,BIND_ERR,LISTEN_ERR,S
};class Sock
{
public:void Socket(){socketfd = socket(AF_INET, SOCK_STREAM, 0);if (socketfd < 0){lg(Fatal, "socket errno : %d ,%s", errno, strerror(errno));exit(SOCK_ERR);}}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;if (bind(socketfd, (const struct sockaddr *)&local, sizeof(local)) < 0){lg(Fatal, "bind errno : %d ,%s", errno, strerror(errno));exit(BIND_ERR);}}void Listen(){int n = listen(socketfd, 10);if (n < 0){lg(Fatal, "listen errno : %d ,%s", errno, strerror(errno));exit(LISTEN_ERR);}}//输出型参数 输出型参数int Accept(std::string *clientip, uint16_t *clientport){struct sockaddr_in remote;socklen_t len = sizeof(remote);int newfd = accept(socketfd, (struct sockaddr *)&remote, &len);if (newfd < 0){lg(Warning, "accept errno : %d ,%s", errno, strerror(errno));return -1;}char buffip[64];inet_ntop(AF_INET, &remote.sin_addr, buffip, sizeof(buffip));*clientip = buffip;*clientport = ntohs(remote.sin_port);return newfd;}bool Connect(std::string &serverip, uint16_t &serverport){struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(serverport);inet_pton(AF_INET, serverip.c_str(), &server.sin_addr);int n = connect(socketfd, (const struct sockaddr *)&server, sizeof(server));if (n < 0){lg(Warning, "connect errno : %d ,%s", errno, strerror(errno));return false;}return true;}int Getfd(){return socketfd;}void Close(){close(socketfd);}private:int socketfd;
};
Log.hpp
日志系统代码如下:
#pragma once
#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
// PrintMethod
#define Screen 1
#define Onefile 2
#define Muchfile 3// leve,指的是日志等级,等级不同处理的方式也不同
#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define LogFile "log.txt"
class Log
{
public:Log(){path = "./log/";_PrintMethod = Screen;}// 用户指定打印方式void AppontPrint(int PrintMethod){_PrintMethod = PrintMethod;}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}void printLog(int level, const std::string &logtxt){switch (_PrintMethod){case Screen:std::cout << logtxt << std::endl;break;case Onefile:printOneFile(LogFile, logtxt);break;case Muchfile:printClassFile(level, logtxt);break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string filename = path + logname;int fd = open(filename.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666);if(fd < 0){return;}int n = write(fd,logtxt.c_str(),logtxt.size());close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // "logtxt.Info/Fatal"printOneFile(filename,logtxt);}void operator()(int level, const char *format, ...){// 自定义部分time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[1024];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);// 默认添加部分va_list s;va_start(s, format);char rightbuffer[1024];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);char logtxt[2024];snprintf(logtxt, sizeof(logtxt), "%s %s\n", leftbuffer, rightbuffer);printLog(level, logtxt);}~Log(){}private:std::string path; // 将路径信息打印到某个路径文件下int _PrintMethod; // 打印的方法(打印到屏幕或文件或多个文件等)
};
2. 制定协议(用于实现序列和反序列化)
Request类
实现的是将 运算的数据 和 运算符 进行序列和反序化。他有三个成员(x_,y_表示运算数据,op_表示运算符)。
const std::string blank_sep = " "; //用于分割 x op y
class Request
{
public:Request(int x, int y, char op): x_(x), y_(y), op_(op){}Request(){}bool Serialize(std::string *out) // x op y , 将对象成员构造成 字符串{std::string s = std::to_string(x_);s += blank_sep;s += op_;s += blank_sep;s += std::to_string(y_);*out = s;return true;}bool Deserialize(std::string &in) // x op y , 反序列将 字符串 构造成对象{// 找到 xsize_t left_blank = in.find(blank_sep);if (left_blank == std::string::npos)return false;std::string x = in.substr(0, left_blank);// 找到ysize_t right_blank = in.rfind(blank_sep);if (right_blank == std::string::npos)return false;std::string y = in.substr(right_blank + 1);if (left_blank + 2 != right_blank) // 左空格和右空格差两个字符return false;op_ = in[left_blank + 1];x_ = std::stoi(x);y_ = std::stoi(y);return true;}void Print(){std::cout << x_ << " " << op_ << " " << y_ << "=?? "<<std::endl;}
public:int x_;int y_;char op_; // + - * /
};
Response类
是将 计算后的结构 和 错误码 进行序列和反序列化 。该类有两个成员(result_表示计算的结果, exitcode_表示错误码用于判断结果的合理性)。
const std::string blank_sep = " "; //用于分割 "result exitcode"
class Response
{
public:Response(int result, int exitcode): result_(result), exitcode_(exitcode){}Response(){}bool Serialize(std::string *out) // result exitcode , 将对象成员构造成 字符串{std::string s = std::to_string(result_);s += blank_sep;s += std::to_string(exitcode_);*out = s;return true;}bool Deserialize(std::string &in) // result exitcode , 反序列将 字符串 构造成对象{// 找到 resultsize_t blank = in.find(blank_sep);if (blank == std::string::npos)return false;std::string result = in.substr(0, blank);// 找到 exitcodestd::string exitcode = in.substr(blank + 1);result_ = std::stoi(result);exitcode_ = std::stoi(exitcode);return true;}public:int result_;int exitcode_;
};
如果只是将 数据以"x op y"或者 "result exitcode"的形式进行数据传输 不好拿出传输的数据,因为你不知道传输的数据是否完整。所以我再给这个有效数据加上 一个包头。
const std::string protocol_sep = "\n"; //用于分割 "len\n"x op y\n// "len"\n"x op y"\n 或者 "len"\n"result exitcode"\n , len表示 "x op y" 或者 "result exitcode" 的长度
std::string Encode(std::string &in)
{std::string s = std::to_string(in.size());s += protocol_sep;s += in;s += protocol_sep;return s;
}bool Decode(std::string &package, std::string *out) //"len"\n"x op y"\n
{size_t left_line = package.find(protocol_sep);if (left_line == std::string::npos)return false;std::string len_str = package.substr(0, left_line);size_t len = std::stoi(len_str);size_t total_size = len_str.size() + len + 2; // 一个有效数据的大小if (package.size() < total_size) // package的大小 一定是大于或等于total_size 的大小的return false;std::string content = package.substr(left_line + 1, left_line + len); // x op y*out = content;package.erase(0,total_size); // 移除已经确定的报文return true;
}
Encode()函数给有效数据 加上 len\n"有效数据"\n ,其中len表示有效数据的大小。Decode()函数则是 取出传输数据中的有效数据,同时会删除传输数据里已经确定的有效数据(即报文)。
总代码如下:
Protocol.hpp头文件
#pragma once
#include <iostream>const std::string blank_sep = " "; //用于分割 x op y
const std::string protocol_sep = "\n"; //用于分割 "len\n"x op y\n// "len"\n"x op y"\n 或者 "len"\n"result exitcode"\n , len表示 "x op y" 或者 "result exitcode" 的长度
std::string Encode(std::string &in)
{std::string s = std::to_string(in.size());s += protocol_sep;s += in;s += protocol_sep;return s;
}bool Decode(std::string &package, std::string *out) //"len"\n"x op y"\n
{size_t left_line = package.find(protocol_sep);if (left_line == std::string::npos)return false;std::string len_str = package.substr(0, left_line);size_t len = std::stoi(len_str);size_t total_size = len_str.size() + len + 2; // 一个有效数据的大小if (package.size() < total_size) // package的大小 一定是大于或等于total_size 的大小的return false;std::string content = package.substr(left_line + 1, left_line + len); // x op y*out = content;package.erase(0,total_size); // 移除已经确定的报文return true;
}
class Request
{
public:Request(int x, int y, char op): x_(x), y_(y), op_(op){}Request(){}bool Serialize(std::string *out) // x op y , 将对象成员构造成 字符串{std::string s = std::to_string(x_);s += blank_sep;s += op_;s += blank_sep;s += std::to_string(y_);*out = s;return true;}bool Deserialize(std::string &in) // x op y , 反序列将 字符串 构造成对象{// 找到 xsize_t left_blank = in.find(blank_sep);if (left_blank == std::string::npos)return false;std::string x = in.substr(0, left_blank);// 找到ysize_t right_blank = in.rfind(blank_sep);if (right_blank == std::string::npos)return false;std::string y = in.substr(right_blank + 1);if (left_blank + 2 != right_blank) // 左空格和右空格差两个字符return false;op_ = in[left_blank + 1];x_ = std::stoi(x);y_ = std::stoi(y);return true;}void Print(){std::cout << x_ << " " << op_ << " " << y_ << std::endl;}
public:int x_;int y_;char op_; // + - * /
};class Response
{
public:Response(int result, int exitcode): result_(result), exitcode_(exitcode){}Response(){}bool Serialize(std::string *out) // result exitcode , 将对象成员构造成 字符串{std::string s = std::to_string(result_);s += blank_sep;s += std::to_string(exitcode_);*out = s;return true;}bool Deserialize(std::string &in) // result exitcode , 反序列将 字符串 构造成对象{// 找到 resultsize_t blank = in.find(blank_sep);if (blank == std::string::npos)return false;std::string result = in.substr(0, blank);// 找到 exitcodestd::string exitcode = in.substr(blank + 1);result_ = std::stoi(result);exitcode_ = std::stoi(exitcode);return true;}public:int result_;int exitcode_;
};
3. 计算(实现计算器功能)
ServerCal.hpp
头文件
ServerCal类实现的是计算器的功能 ,服务器得到客户端传输过来的数据后,通过调用该类的函数对数据计算处理 ,并将结果返回给客户端。
#pragma Once
#include <iostream>
#include "Protocol.hpp"enum
{Div_Zero = 1,Mod_Zero,Other_Oper
};class ServerCal
{
public:Response Headler(Request &req){Response res(0, 0);switch (req.op_){case '+':res.result_ = req.x_ + req.y_;break;case '-':res.result_ = req.x_ - req.y_;break;case '*':res.result_ = req.x_ * req.y_;break;case '/':{if (req.y_ == 0)res.exitcode_ = Div_Zero;elseres.result_ = req.x_ / req.y_;}break;case '%':{if (req.y_ == 0)res.exitcode_ = Mod_Zero;elseres.result_ = req.x_ % req.y_;}break;default:res.exitcode_ = Other_Oper;break;}return res;}std::string Calculator(std::string &package) // 将报文处理成我们想要的结构{std::string content;bool n = Decode(package, &content);if (!n)return ""; // Decode 出现问题返回一个空字符串Request req;req.Deserialize(content); // 将 x op y 反序列化成 对象。Response res = Headler(req); // 处理 request 对象,将 数据转换成 response对象content = "";res.Serialize(&content); // 将 response 序列化成字符串content = Encode(content); // "len\n"result exitcodereturn content;}
};
服务端得到客户端传过来的报文后,调用Calculator()函数,将 计算双方 转换成 结果返回给服务器 。
4. 服务器(将上面所有的类功能调用起来)
TcpServer.hpp
头文件
TcpServer类中有三个成员函数,其中 Sock listenfd_表示监听文件描述符(注意它是Sock类型),port_表示服务器端口号 , callback_表示回调函数 (该函数是ServerCal中的Calculator函数,由服务端传递,这样做可以有效解耦)。
#pragma Once
#include <unistd.h>
#include <signal.h>
#include <functional>
#include "Socket.hpp"
#include "Log.hpp"extern Log lg;using func_t = std::function<std::string(std::string &package)>; // 回调函数// 传 ServerCal.hpp类中的 Calulator函数class TcpServer
{
public:TcpServer(uint16_t port, func_t callback): port_(port), callback_(callback){}// 服务器初始化 TCP协议void InitServer(){listenfd_.Socket();listenfd_.Bind(port_);listenfd_.Listen();lg(Info, " InitServer done errno:%d ,%s", errno, strerror(errno));}void Start(){signal(SIGCHLD, SIG_IGN);signal(SIGPIPE, SIG_IGN);while (true){// 连接客户端std::string clientip;uint16_t clientport;int sockfd = listenfd_.Accept(&clientip, &clientport);if (sockfd < 0){lg(Warning, "accept errno:&d ,%s", errno, strerror(errno));continue;}lg(Info, "accept a new link, sockfd: %d, clientip: %s, clientport: %d", sockfd, clientip.c_str(), clientport);if (fork() == 0) // 子进程来执行任务{listenfd_.Close(); // 子进程关闭 listenfdstd::string inbuffer_stream; //传输的数据存放处while (true){char inbuff[128];ssize_t n = read(sockfd, inbuff, sizeof(inbuff));if (n > 0){inbuff[n] = 0;inbuffer_stream += inbuff;lg(Debug, "debug:\n%s", inbuffer_stream.c_str());// 将读到的数据 写入到客户端中 , 直到缓存区的数据读取完。while (true){std::string info = callback_(inbuffer_stream); // "len\n"result exitcodeif (info.empty())break;lg(Debug, "debug, response:\n%s", info.c_str());lg(Debug, "debug:\n%s", inbuffer_stream.c_str());ssize_t w = write(sockfd, info.c_str(), info.size());}}else if (n <= 0)break;}exit(0); // 执行完任务的子进程退出}// 父进程 -----close(sockfd);}}private:Sock listenfd_;uint16_t port_;func_t callback_; // 回调函数
};
提供创造子进程来执行任务。
5. 服务端
ServerCal.cc
头文件
#pragma Once
#include "ServerCal.hpp"
#include "TcpServer.hpp"#include <iostream>
#include <sys/socket.h>
void Usage(std::string proc)
{std::cout << "Usage :" << proc << " serverport" << std::endl;
}
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);return -1;}uint16_t port = std::stoi(argv[1]);ServerCal cal;TcpServer* tcp = new TcpServer(port, std::bind(&ServerCal::Calculator, &cal, std::placeholders::_1));tcp->InitServer();tcp->Start();return 0;
}
6.客户端
Client.cc
头文件
#pragma Once
// #include <time.h>
#include <unistd.h>
#include "Socket.hpp"
#include "Protocol.hpp"void Usage(std::string proc)
{std::cout << "Usage :" << proc << " serverip serverport" << std::endl;
}
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]);return -1;}uint16_t port = std::stoi(argv[2]);std::string ip = argv[1];//连接服务器Sock sockfd;sockfd.Socket();sockfd.Connect(ip, port);// ------const std::string opers = "+-*/%=-=&^"; //操作符的范围srand(time(nullptr)); int cnt = 5;std::string buff_stream; //用于存放传输的数据while (cnt--){std::cout << "------------- 测试开始---cnt: " << cnt << std::endl;int x = rand() % 100 + 1;int y = rand() % 100;usleep(66666);char op = opers[rand() % opers.size()];//发送数据给服务器std::string content; Request req(x, y, op);req.Serialize(&content); // 序列化content = Encode(content);req.Print();ssize_t n = write(sockfd.Getfd(), content.c_str(), content.size());if (n < 0){std::cerr << "write fail" << std::endl;}// std::cout << "这是最新的发出去的请求 " << "\n" ;// write(sockfd.Getfd(), content.c_str(), content.size());// 读取数据char inbuff[1280];n = read(sockfd.Getfd(), inbuff, sizeof(inbuff));if (n > 0){inbuff[n] = 0;buff_stream += inbuff;// std::cout << buff_stream << std::endl;std::string info;bool r = Decode(buff_stream, &info);if (!r){break;}Response res;res.Deserialize(info);std::cout << res.result_ << " " << res.exitcode_ << std::endl;}else{break;}std::cout << "----------------------------------------" << std::endl;sleep(1);}return 0;
}
运行效果图:
(三) Json
自己定义序列化和反序列化过于繁琐,我们可以调用其他的库来完成序列化和反序列化。
比如: Json、Protobuf 。
下面我们提供Json库来完成序列和反序列化。
1.使用例子
提供调用下面命令来现在Json库。
yum install -y jsoncpp-devel
使用该库要引入<jsoncpp/json/json.h>
头文件
实例一:
#include<iostream>
#include<jsoncpp/json/json.h>
int main()
{Json::Value part1;part1["haha"] = "haha";part1["hehe"] = "hehe";Json::Value root;root["x"] = 100;root["y"] = 200;root["op"] = '+';root["desc"] = "this is a + oper";root["test"] = part1;Json::FastWriter w;//Json::StyleWriter w;std::string res = w.write(root);std::cout << res << std::endl;return 0;
}
实例二:
int main()
{Json::Value part1;part1["haha"] = "haha";part1["hehe"] = "hehe";Json::Value root;root["x"] = 100;root["y"] = 200;root["op"] = '+';root["desc"] = "this is a + oper";root["test"] = part1;Json::FastWriter w;std::string res = w.write(root);sleep(3);Json::Value v;Json::Reader r;r.parse(res, v);int x = v["x"].asInt();int y = v["y"].asInt();char op = v["op"].asInt();std::string desc = v["desc"].asString();Json::Value temp = v["test"];std::cout << x << std::endl;std::cout << y << std::endl;std::cout << op << std::endl;std::cout << desc << std::endl;return 0;
}
2.使用json实现网络计算器的序列和反序列
makefile
.PHONY:all
all:server clientFlag=#-DMySelf=1server:ServerCal.ccg++ -o $@ $^ -std=c++11 -ljsoncpp $(Flag)
client:Client.ccg++ -o $@ $^ -std=c++11 -ljsoncpp $(Flag).PHONY:clean
clean:rm -f server client
protocol.hpp
#pragma once
#include <iostream>
#include<jsoncpp/json/json.h>//#define MySelf 1const std::string blank_sep = " "; //用于分割 x op y
const std::string protocol_sep = "\n"; //用于分割 "len\n"x op y\n// "len"\n"x op y"\n 或者 "len"\n"result exitcode"\n , len表示 "x op y" 或者 "result exitcode" 的长度
std::string Encode(std::string &in)
{std::string s = std::to_string(in.size());s += protocol_sep;s += in;s += protocol_sep;return s;
}bool Decode(std::string &package, std::string *out) //"len"\n"x op y"\n
{size_t left_line = package.find(protocol_sep);if (left_line == std::string::npos)return false;std::string len_str = package.substr(0, left_line);size_t len = std::stoi(len_str);size_t total_size = len_str.size() + len + 2; // 一个有效数据的大小if (package.size() < total_size) // package的大小 一定是大于或等于total_size 的大小的return false;std::string content = package.substr(left_line + 1, left_line + len); // x op y*out = content;package.erase(0,total_size); // 移除已经确定的报文return true;
}
class Request
{
public:Request(int x, int y, char op): x_(x), y_(y), op_(op){}Request(){}bool Serialize(std::string *out) // x op y , 将对象成员构造成 字符串{
#ifdef MySelf std::string s = std::to_string(x_);s += blank_sep;s += op_;s += blank_sep;s += std::to_string(y_);*out = s;
#elseJson::Value root;root["x"] = x_;root["y"] = y_;root["op"] = op_;Json::StyledWriter w;*out = w.write(root);#endifreturn true;}bool Deserialize(std::string &in) // x op y , 反序列将 字符串 构造成对象{
#ifdef MySelf // 找到 xsize_t left_blank = in.find(blank_sep);if (left_blank == std::string::npos)return false;std::string x = in.substr(0, left_blank);// 找到ysize_t right_blank = in.rfind(blank_sep);if (right_blank == std::string::npos)return false;std::string y = in.substr(right_blank + 1);if (left_blank + 2 != right_blank) // 左空格和右空格差两个字符return false;op_ = in[left_blank + 1];x_ = std::stoi(x);y_ = std::stoi(y);
#elseJson::Value root;Json::Reader r;r.parse(in,root);x_ = root["x"].asInt();y_ = root["y"].asInt();op_ = root["op"].asInt();#endifreturn true;}void Print(){std::cout << x_ << " " << op_ << " " << y_ << "=?? "<<std::endl;}
public:int x_;int y_;char op_; // + - * /
};class Response
{
public:Response(int result, int exitcode): result_(result), exitcode_(exitcode){}Response(){}bool Serialize(std::string *out) // result exitcode , 将对象成员构造成 字符串{
#ifdef MySelf std::string s = std::to_string(result_);s += blank_sep;s += std::to_string(exitcode_);*out = s;
#elseJson::Value root;root["result"] = result_;root["exit"] = exitcode_;Json::StyledWriter w;*out = w.write(root);#endifreturn true;}bool Deserialize(std::string &in) // result exitcode , 反序列将 字符串 构造成对象{
#ifdef MySelf // 找到 resultsize_t blank = in.find(blank_sep);if (blank == std::string::npos)return false;std::string result = in.substr(0, blank);// 找到 exitcodestd::string exitcode = in.substr(blank + 1);result_ = std::stoi(result);exitcode_ = std::stoi(exitcode);
#elseJson::Value root;Json::Reader r;r.parse(in,root);result_ = root["result"].asInt();exitcode_ = root["exit"].asInt();#endifreturn true;}public:int result_;int exitcode_;
};
运行效果:
四) OSI7层模型
我们计算功能(ServerCal.hpp)相当于应用层,制定的序列化和反序列化(Protocol.hpp)相当于表示层,TcpServer.hpp服务器相当于会话层。
这就是为什么上面这三层是由我们自己定义的统称为应用层。