2021SC@SDUSC
目录
一. 简单介绍序列化与反序列化
二.协议与序列化的关系
三.关于workflow的序列化接口
需要IDL的使用过程:
encode函数:
append函数
errno的设置
一. 简单介绍序列化与反序列化
对象的序列化(Serialization),即把一个对象以流的方式,写入到文件中保存,叫写对象,也叫对象序列化,对象中包含的不仅仅是字符,使用字节流。
对象的反序列化(deserailization),即把文件中保存的对象,以流的方式读取出来,叫做读取对象,也叫对象的反序列。读取的文件保存的都是字节,使用字节流。
对象序列化的作用:在传递,和保存对象(object)的时候,保证对象的完整性和可传递性。
在翻看其它文章时发现底下有个评论很棒,概括得很全面:序列化是指把一个对象变成二进制内容,本质上就是一个byte[]数组。 为什么要把对象序列化呢?因为序列化后可以把byte[]保存到文件中,或者把byte[]通过网络传输到远程,这样,就相当于把对象存储到文件或者通过网络传输出去了。 有序列化,就有反序列化,即把一个二进制内容(也就是byte[]数组)变回对象。有了反序列化,保存到文件中的byte[]数组又可以“变回对象,或者从网络上读取byte[]并把它“变回”对象。
二.协议与序列化的关系
关于协议:需要在发送请求的时候定一个边界,在请求收到的时候按照这个设定的边界进行数据分割,避免语义不一致的事情发生,而这个边界语义的表达,就是所谓的协议。
协议层是凌驾于序列化层之上的一层。协议层和序列化层之间还有传输层,动态代理层。
protocol 层主要用于配置 refer(发现服务) 和 exporter(暴露服务) 的实现方式,transport 层定义了传输的方式,codec 层诠释了具体传输过程中报文解析的方式,serialize 层负责将对象转换成字节,以用于传输,proxy 层负责将这些细节屏蔽。
它们的包含关系如下:protocol > transport > codec > serialize
用户自定义协议,需要提供协议的协议的序列化和反序列化方法。
三.关于workflow的序列化接口
实现序列化的方案有很多,如JSON/XML,众多的序列化方案中,按照存储方案,可分为字符串存储和二进制存储,字符串存储是可读的,但是由于以上问题,这里只考虑二进制存储。二进制存储中可分为需要IDL和不需要IDL,或分为自描述与非自描述(反序列化是否需要IDL)。
workflow采取的是IDL序列化:
需要IDL的使用过程
- 使用该方案所定义的IDL语法编写schema
- 使用该方案提供的编译器将schema编译成生产方和消费方所用语言的代码(类或者模块)
- 数据生产方引用该代码,根据其接口,构建数据,并序列化
- 消费方引用该代码,根据其接口读取数据
在workflow中, 在ProtocolMessage.h里,序列化反序列化接口如下:
namespace protocol
{
class ProtocolMessage : public CommMessageOut, public, CommMessageIn
{
private:
virtual int encode(struct iovec vectors[], int max) = 0;//encode函数为序列化函数
virtual int append(const char *buf, size_t size) = 0;//append函数为反序列化函数
...
};
}
在上述接口中定义了两个函数,其中encode函数为序列化函数,append函数为反序列化函数。
encode函数:
- encode函数在消息被发送之前调用,每条消息只调用一次。
- encode函数里,用户需要将消息序列化到一个vector数组,数组元素个数不超过max。目前max的值为8192。
- 结构体struct iovec定义在请参考系统调用readv和writev。
- encode函数正确情况下的返回值在0到max之间,表示消息使用了多少个vector。
- 如果是UDP协议,请注意总长度不超过64k,并且使用不超过1024个vector(Linux一次writev只能1024个vector)。
- UDP协议只能用于client,无法实现UDP server。
- encode返回-1表示错误。返回-1时,需要置errno。如果返回值>max,将得到一个EOVERFLOW错误。错误都在callback里得到。
- 为了性能考虑vector里的iov_base指计指向的内容不会被复制。所以一般指向消息类的成员。
- 如果是UDP协议,请注意总长度不超过64k,并且使用不超过1024个vector(Linux一次writev只能1024个vector)。
- UDP协议只能用于client,无法实现UDP server。
扩展知识:IO流程中的io向量:iovec
为了提高从磁盘读取数据到内存的效率,引入了IO向量机制,IO向量即struct iovec,主要在API接口在readv和writev中使用。
struct iovec成为IO向量,其结构如下;
#include
struct iovec{
void *iov_base;
size_t iov_len;
}
很明显,iov_base作为一个指针,用于记录buffer地址(一般为用户态地址),iov_len表示buffer的大小。
在用户态或内核态中使用时,一般均采用指针的形式:struct iovec *iov。当然一般也给出其元素的个数count。
append函数
- append函数在每次收到一个数据块时被调用。因此,每条消息可能会调用多次。
- buf和size分别是收到的数据块内容和长度。用户需要把数据内容复制走。
- 如果是UDP协议,一次append一定是一个完整的数据包。
- append函数返回0表示消息还不完整,传输继续。返回1表示消息结束。-1表示错误,需要置errno。
- 总之append的作用就是用于告诉框架消息是否已经传输结束。不要在append里做复杂的非必要的协议解析。
- 如果是UDP协议,一次append一定是一个完整的数据包。
errno的设置
- encode或append返回-1或其它负数都会被理解为失败,需要通过errno来传递错误原因。用户会在callback里得到这个错误。
- 如果是系统调用或libc等库函数失败(比如malloc),libc会肯定会设置好errno,用户无需再设置。
- 一些消息不合法的错误是比如常见的,比如可以用EBADMSG,EMSGSIZE分别表示消息内容错误,和消息太大。
- 用户可以选择超过系统定义errno范围的值来表示一些自定义错误。一般大于256的值是可以用的。
- 请不要使用负数errno。因为框架内部用了负数来代表SSL错误(证书出现错误)。
------------------------------------------------------------------------
参考文献:序列化和反序列化 - 知乎 (zhihu.com)
帅枫 (cnblogs.com)
(41条消息) 序列化和反序列化的详解_tree_ifconfig的博客-CSDN博客_序列化和反序列化
docs/tutorial-10-user_defined_protocol.md · 搜狗开源/workflow - Gitee.com
IO流程中IO向量iovec - YoungerChina - 博客园 (cnblogs.com)



