RPC框架中的消息传递和协议


RPC是两个子系统之间进行的直接信息交互,它使用操作系统提供的套接字来作为消息的载体,以特定

的消息格式来定义消息的内容和边界;客户端和服务端都是通过文件描述符的读写API来访问操作系统内核

中的网络模块为当前的套接字分配的发送(send buffer)和接收(recv buffer)缓存

3DilFO.jpg

具体在这个过程中消息的内容是什么就需要我们深入RPC的协议进行学习了

协议设计的整体思想

对于一串消息流,我们必须能确定消息边界,提取出单条消息的字节流片段,然后对这个片段按照一

定的规则进行反序列化来生成相应的消息对象;消息的表示就是序列化后的消息字节流在直观上的表现

形式,文本的形式对人类比较友好,二进制的形式对计算机比较友好

每个消息都有其内部字段的结构,结构构成了消息内部的逻辑规则,程序要按照结构的规则来决定字段

序列化后的顺序

一、消息边界

RPC需要在一条TCP链接上进行多次消息传递,在连续的两条消息之间必须有明确的分隔符规则,以便

接受端可以将消息分割开来;基于TCP连接之上的单条消息如果过大,就会被网络协议栈拆分成多个数据包

进行传送,而如果消息过小,协议栈就有可能将多个消息合成一个数据包进行发送。对于接收端来说,它看

到的只是一串串的字节数组,如果没有明确的消息边界规则,接收端就不知道这一串字节数组包含了多少消息

比较常用的两种分割方式是特殊分隔符法和长度前缀法,分别如下图所示

3DatjH.jpg

特殊符分割法就是在每条消息的末尾追加一个特殊的分隔符,并且保证消息中间的数据不能包含特殊分

割符。比如最常见的分隔符是“ \r\n ”,当接受端遍历字节数组时发现了“\r\n”,就可以确定这个分隔符之前的

字节数组是一条完整的消息。在HTTP和Reids协议中就大量了使用“\r\n”分隔符,这种应用场景要求消息体的

内容是文本消息

这种方法的优点是消息的可读性比较强,可以直接看到消息的文本内容,但是不适合传递二进制消息,

因为二进制的字节数组中很容易出现“\r\n”分隔符的ASCII值,如果非要传递的话需要对二进制进行base64

编码转换成文本消息再进行传送

3DaaDA.jpg

消息发送端在每条消息的开头再增加4字节长度的整数值,标记消息体的长度,这样消息的接受者就会

首先读取长度信息,然后再读取相应长度的字节数组就可以将一个完整的消息分离出来,常用于二进制消息

这种方法的优缺点与特殊分割符法正好相反。长度前缀法可读性很差,但是适用于二进制协议,但是

对于文本和内容都可以进行传递

HTTP协议是上述两种法的混合型协议,因为HTTP的消息头采用的是纯文本外加“\r\n”分隔符,而消息

体是通过消息头中的Content-length的值来决定长度的。HTTP协议可以传输文本协议,也可以传输二进制

数据,比如常见的音视频图像,所以被称为超文本传输协议

二、消息结构

每条消息都有着包含它的语义结构信息,有些消息协议的结构信息是显示的,还有些是隐式的。JSON

消息的结构就可以直接通过它的内容体现出来,可读性非常高,但是它有太多的冗余信息,比如每个字符

串都使用双引号界定边界,键值对之间必须有冒号进行分割,对象之间必须使用大括号进行分割;而且连

续的多条json消息即使结构完全一样,仅是value值不同,也需要发送同样的key字符串

消息的结构在同一条消息通道上是可以进行复用的,比如建立在链接的开始的RPC的客户端和

服务端之间先互相告知消息的结构,后续发送消息时只需要按照这个消息的模板发送消息就可以了,

接收端会自动将value值与相应位置的key关联起来,这样的模式可以节省比较多的流量

消息的隐式结构就是指那些结构信息由代码来约定的消息协议,在RPC交互的消息数据中只是纯粹的

二进制数据,由代码来确定对应位置的二进制是属于哪个字段的;单纯看消息的内容是无法知道具体字节

的含义的,它的消息结构是通过代码的结构顺序来确定的,这样的方式也可以节省传输的流量

三、消息压缩

如果消息的内容太大,就要考虑对消息进行压缩处理,这样可以减轻网络带宽,但是增大CPU的压力;

这两者之间一定要学会权衡。此外最好使用C语言实现的算法库,这样这样执行起来是比较快的,比较出名

的是Google的snappy算法