目录
前言:
在进行网络通信时,应用层协议是需要程序员手动定制的,序列化与反序列化是对协议进行字符串形式的转换。应用层协议表示发送端和接收端共同遵守的规则,依照这个规则,通信的双方才能够分析出传输数据的真正含义,只有理解了数据的真正含义,才能对数据进行处理,因此应用层协议在网络通信中非常重要。传输层协议为TPC\UDP,网络层协议为ipv4\ipv6,这些协议都是设置好的,而应用层协议需要程序员手动实现,传输应用层协议需要进行序列化与反序列化。
1、序列化与反序列化概念
序列化与反序列化是发送端和接收端对网络中传输数据的处理,是一种对传输数据的解释行为,通信双方制定好协议后,发送端需要将协议化的数据通过序列化传给接收端,而接收端拿到数据后需要对其进行反序列化以拿到协议的对应内容,具体示意图如下:
如上图所示,序列化和反序列化实际上就是结构体->字符串,字符串->结构体的过程。
2、正、反序列化的目的
反序列化的目的好理解,因为数据在传输前进行了正序列化,此时数据已经被“修饰”了,那么接收端必须采用同样的方式去掉“修饰”才能拿到有效数据,把这种行为叫做反序列化。那么为什么在传输数据前要将协议进行序列化呢?
答:因为结构体类型在不同平台上的大小、存放形式不一样,因此不能直接将结构体传输给对方,并且结构体难以用字节流的形式在网络上传输。所以要进行序列化将其转换成字符串的形式传递。
3、添加报头的目的
上图中对协议内容进行添加报头,该行为的目的是为了接收方可以更好的解析序列化内容,还能够给在报头中进行数据加密,提供安全性。最主要的是若传输过程中出现数据丢失,则可以通过报头里的信息来判断是否发生数据丢失, 提供了数据在传输过程中的完整性。另外,还可以提供此次数据的版本号等等。
4、实现正、反序列化思路
我们可以实现一个简单的计算机类,因为正反序列化是要在网络传输的过程中发挥作用的,为了能够更简单的描绘出正反序列化,所以我们在一个main函数中将整个过程模拟出来,方便理解。
实现正、反序列化的思路:首先通信双方必须遵守同一个协议,即有同一个结构体类型,发送端发送的数据是由该结构体的成员变量进行序列化后再发送的,接收端拿到数据后先进行反序列化,然后添加到协议(结构体)里,此时说明接收端已经具备了处理数据的能力。如下图:
5、代码实现
计算机类如下:
#pragma once
#include <iostream>
#include <string>
//用“ ”和“\n”表示协议的具体规定
const std::string blank_space_sep = " ";
const std::string protocol_sep = "\n";
class Request
{
public:
Request(int data1, int data2, char oper)
: x(data1), y(data2), op(oper)
{
}
Request()
{
}
public:
bool Serialize(std::string *out)//序列化
{
// struct => string, "x op y"
//根据协议的规定进行序列化
std::string s = std::to_string(x);
s += blank_space_sep;
s += op;
s += blank_space_sep;
s += std::to_string(y);
*out = s;
return true;
}
bool Deserialize(const std::string &in) //反序列化
{
//根据协议的规定拿到有效数据
std::size_t left = in.find(blank_space_sep);
if (left == std::string::npos)
return false;
std::string part_x = in.substr(0, left);
std::size_t right = in.rfind(blank_space_sep);
if (right == std::string::npos)
return false;
std::string part_y = in.substr(right + 1);
if (left + 2 != right)
return false;
//添加到当前的成员变量中
op = in[left + 1];
x = std::stoi(part_x);
y = std::stoi(part_y);
return true;
}
void DebugPrint()
{
std::cout << "新请求构建完成: " << x << op << y << "=?"
<< std::endl;
}
public:
// x op y
int x;
int y;
char op; // + - * / %
};
模拟序列化与反序列化的过程如下:
#include "app1.hpp"
#include <unistd.h>
int main(int argc, char *argv[])
{
//模拟客户端序列化
Request req(12364566, 43454356, '+');
std::string s;
req.Serialize(&s);
std::cout << s <<std::endl;
//模拟服务器反序列化
Request temp;
temp.Deserialize(s);
temp.DebugPrint();
return 0;
}
运行结果:
6、添加报头、去除报头
添加报头的逻辑:将数据的长度作为报头的内容,并且用\n将报头内容与数据做分隔,这样接收端就可以根据第一个\n来拿到报头了。
去除报头的逻辑:根据\n来拿到报头的内容,因为报头的内容是数据的长度,因此可以根据报头内容将数据从中取出。
添加报头、去除报头的代码如下:
//添加报头:将数据长度作为报头内容,并用\n分隔开来
//(“len”\n"x op y"\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;
}
//根据报头内容以及\n,去掉报头,从而拿到数据本身
bool Decode(std::string &package, std::string *content)
{
std::size_t pos = package.find(protocol_sep);
if (pos == std::string::npos)//数据不完整,返回
return false;
std::string len_str = package.substr(0, pos);
std::size_t len = std::stoi(len_str);
std::size_t total_len = len_str.size() + len + 2;
if (package.size() < total_len)//数据不完整,返回
return false;
*content = package.substr(pos + 1, len);
// 走到此次说明数据正确被取出,因此package里的数据可以清空
package.erase(0, total_len);
return true;
}
运行结果:
结语
以上就是关于网络通信应用层协议-序列化与反序列化的讲解,序列化与反序列化并不代码应用层协议本身,但是他与应用层协议密切相关,应用层协议是通信双方对传输的数据格式达成一致观念,而序列化与反序列化是进行数据传输的一种手段,他只一种行为。
最后如果本文有遗漏或者有误的地方欢迎大家在评论区补充,谢谢大家!!