文章目录
- 0. 前言
- 1. 认识协议
- 2. 序列号与反序列化
- 3. 自定义协议——网络计算器
- 4. json
本章Gitee仓库:序列反序列化
0. 前言
tcp
是面向字节流的,但是如何保证读取的数据是一个完整的报文呢?
管道也是面向字节流,写端写了一大堆的数据,然后读端可能一下子全部读上来了,这一堆数据如何区分,是一个需要考虑的问题
tcp
是传输控制协议,它要管:
- 什么时候发送
- 发送多少数据
- 如果出错了如何解决
我们用户在应用层,通过write
将数据写到tcp
的发送缓冲区,然后write
直接返回,但是这个数据并不一定发送给对方了,write
只是将数据从用户拷贝到内核,而具体什么时候发、怎么发,都是由tcp
决定,因为只有tcp
才清楚当前网络的状况和接收方的接收能力。
而
tcp
是操作系统的网络模块部分,就可以理解为,用户将数据给了操作系统
接收方通过read
将接收缓冲区的数据拷贝到用户层的缓冲区,怎么读这些报文是个问题。
所以需要在用户层需要定制协议。
1. 认识协议
比如说要实现一个网络计算器,一个算式要包括数据和运算符号,例如1 + 1
、2 * 2
…
那就可以定义一个结构,规定的每次发送的数据格式:
struct task
{int data1; //数据1int data2; //数据2char op; //运算符
}
然后接收方那边有一个相同的结构体进行接收,然后将数据提取出来即可,然后计算的结果,也要规定格式,例如是否正确,如果不正确是什么原因导致:
struct calc
{int result; //结果int code; //退出码
}
以上这种以结构化的方式把约定表达出来,这就是协议。
2. 序列号与反序列化
对于不同的平台struct
的大小可能不一样,有的可能会内存对齐,有的又不会,所以不能够直接将结构体对象发送给对方。
例如我们微信聊天的时候:
我们发送的消息不只是消息本身,还携带了发送的数据,谁发送的,以及我们的头像
struct msg
{string info; //消息string nickname; //昵称string time; //时间
}
这里是三个字符串,肯定是不能分开发的,不然消息就混乱了,所以就需要打个包,将这三个字符串转成一个字符串发送,这叫序列化;接收方收到这一个字符串之后,就将这一个字符串再解析成三个字符串,这就叫反序列化
序列反序列化的主要目的就是方便网络进行接收。
所以在定制协议的时候,只需要考虑定制什么样的结构化数据,然后将这个结构化的数据序列化成一个大字符串。
这里面也有一个隐含的问题,就是如何将这个大字符串反序列化,所以我们在发的时候,要设置好标记
3. 自定义协议——网络计算器
网络版本的计算机,就要涉及到数据包的发送,所以这里就需要定制协议。
要将一个结构化的数据转成字符串,就要设置后间隔标志,以便于反序列化:
protocol.hpp:
#pragma once#include<iostream>const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";//添加报头
std::string Encode(std::string &content)
{std::string package = std::to_string(content.size());package += protocol_sep;package += content;package += protocol_sep;return package;
}//去掉报头
bool Decode(std::string &package, std::string *content)
{//"len"\n"x op y"\nsize_t pos = package.find(protocol_sep);if(pos == std::string::npos) return false;std::string len_str = package.substr(0, pos);size_t len = std::stoi(len_str);size_t total_len = len_str.size() + len + 2;if(package.size() < total_len) return false;*content = package.substr(pos+1, len);//移除已提取报文package.erase(0, total_len);return true;
}class Request
{
public:Request(int data1, int data2, char op):data1_(data1),data2_(data2),op_(op){}Request(){}
public://序列化bool Serialize(std::string *out){//有效载荷//x op ystd::string s = std::to_string(data1_);s += blank_space_sep;s += op_;s += blank_space_sep;s += std::to_string(data2_);*out = s;return true;}//反序列化bool Deserialize(const std::string &in){//"data1 op data2"size_t leftpos = in.find(blank_space_sep);if(leftpos == std::string::npos) return false;std::string part1 = in.substr(0, leftpos);size_t rightpos = in.rfind(blank_space_sep);if(rightpos == std::string::npos) return false;std::string part2 = in.substr(rightpos+1);if(leftpos+1 != rightpos-1) return false;op_ = in[leftpos+1];data1_ = std::stoi(part1);data2_ = std::stoi(part2);return true;}void DebugPrint(){std::cout << "receive a request: " << data1_ << op_ << data2_ << std::endl;}
public:int data1_;int data2_;char op_;
};class Response
{
public:Response(int res, int code):result_(res),exitcode_(code){}Response(){}
public://序列化bool Serialize(std::string *out){//有效载荷//"result exitcode"std::string s = std::to_string(result_);s += blank_space_sep;s += std::to_string(exitcode_);*out = s;return true;}//反序列化bool Deserialize(const std::string &in){//"result exitcode"size_t pos = in.find(blank_space_sep);if(pos == std::string::npos) return false;std::string part1 = in.substr(0, pos);std::string part2 = in.substr(pos+1);result_ = std::stoi(part1);exitcode_ = std::stoi(part2);return true;}void DebugPrint(){std::cout << "ret calc done, result: " << result_ << " exitcode: " << exitcode_ << std::endl;}public:int result_;int exitcode_;
};
这个协议可以自己定,下面稍微讲解一下本次自定义的协议
这里规定,以空格为分隔符:
data1 op data2
,需要向网络发送的时候,会添加一个报头,格式为"字符串长度"\n报文\n
;收到这个请求之后,先将这个大字符串反序列化,提取data1 op data2
,当然这里可能解析的不是一个完整的报文,所以需要判断解析的报文不超过原始字符串的长度。对于计算结果的序列和反序列化,比请求的稍微简单一点,这里就不多说了,代码里面有注释
运行演示:
4. json
当然序列反序列化也不用每次我们自己手动去做,我们可以采用json
。
C++要使用json
,是需要安装第三方库的jsoncpp
:
sudo yum install -y jsoncpp-devel
test.cc:
#include<iostream>
#include<jsoncpp/json/json.h>int main()
{Json::Value js;js["json"] = "js";js["aaa"] = "AAA";Json::Value v1;v1["data1"] = 100;v1["data2"] = 20;v1["op"] = '+';v1["rejs"] = js; //json里面套json//序列化//Json::FastWriter fw;Json::StyledWriter sw;std::string ser = sw.write(v1);std::cout << ser << std::endl;//反序列化Json::Value v2;Json::Reader r;//参数1:需要反序列化的字符串 参数2:反序列化的结果 参数3:直接缺省先r.parse(ser, v2);int data1 = v2["data1"].asInt();int data2 = v2["data2"].asInt();char op = v2["op"].asInt();Json::Value tmp = v2["rejs"];std::string s1 = tmp["json"].asString();std::string s2 = tmp["aaa"].asString();std::cout << data1 << std::endl;std::cout << data2 << std::endl;std::cout << op << std::endl;std::cout << s1 << std::endl;std::cout << s2 << std::endl;return 0;
}
这样就可以使用条件编译,来选择是用我们自己写的序列反序列化,还是直接使用json
的
编译的时候加上
-D
选项,例如要定义#define Myself 1
,就可以加上-DMyself=1
#pragma once#include<iostream>
#include<jsoncpp/json/json.h>
//#define Myself 1const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";//添加报头
std::string Encode(std::string &content)
{std::string package = std::to_string(content.size());package += protocol_sep;package += content;package += protocol_sep;return package;
}//去掉报头
bool Decode(std::string &package, std::string *content)
{//"len"\n"x op y"\nsize_t pos = package.find(protocol_sep);if(pos == std::string::npos) return false;std::string len_str = package.substr(0, pos);size_t len = std::stoi(len_str);size_t total_len = len_str.size() + len + 2;if(package.size() < total_len) return false;*content = package.substr(pos+1, len);//移除已提取报文package.erase(0, total_len);return true;
}class Request
{
public:Request(int data1, int data2, char op):data1_(data1),data2_(data2),op_(op){}Request(){}
public://序列化bool Serialize(std::string *out){
#ifdef Myself//有效载荷//x op ystd::string s = std::to_string(data1_);s += blank_space_sep;s += op_;s += blank_space_sep;s += std::to_string(data2_);*out = s;return true;
#elseJson::Value root;root["data1"] = data1_;root["data2"] = data2_;root["op"] = op_;Json::FastWriter fw;*out = fw.write(root);return true;
#endif}//反序列化bool Deserialize(const std::string &in){
#ifdef Myself//"data1 op data2"size_t leftpos = in.find(blank_space_sep);if(leftpos == std::string::npos) return false;std::string part1 = in.substr(0, leftpos);size_t rightpos = in.rfind(blank_space_sep);if(rightpos == std::string::npos) return false;std::string part2 = in.substr(rightpos+1);if(leftpos+1 != rightpos-1) return false;op_ = in[leftpos+1];data1_ = std::stoi(part1);data2_ = std::stoi(part2);return true;
#elseJson::Value root;Json::Reader r;r.parse(in, root);data1_ = root["data1"].asInt();data2_ = root["data2"].asInt();op_ = root["op"].asInt();return true;
#endif}void DebugPrint(){std::cout << "receive a request: " << data1_ << op_ << data2_ << std::endl;}
public:int data1_;int data2_;char op_;
};class Response
{
public:Response(int res, int code):result_(res),exitcode_(code){}Response(){}
public://序列化bool Serialize(std::string *out){
#ifdef Myself//有效载荷//"result exitcode"std::string s = std::to_string(result_);s += blank_space_sep;s += std::to_string(exitcode_);*out = s;return true;
#elseJson::Value root;root["result"] = result_;root["exitcode"] = exitcode_;Json::FastWriter fw;*out = fw.write(root);return true;
#endif}//反序列化bool Deserialize(const std::string &in){
#ifdef Myself//"result exitcode"size_t pos = in.find(blank_space_sep);if(pos == std::string::npos) return false;std::string part1 = in.substr(0, pos);std::string part2 = in.substr(pos+1);result_ = std::stoi(part1);exitcode_ = std::stoi(part2);return true;
#elseJson::Value root;Json::Reader r;r.parse(in, root);result_ = root["result"].asInt();exitcode_ = root["exitcode"].asInt();return true;
#endif}void DebugPrint(){std::cout << "ret calc done, result: " << result_ << " exitcode: " << exitcode_ << std::endl;}public:int result_;int exitcode_;
};
本章自定义的协议,就是属于
OSI
七层模型中的表示层