前面我们介绍了 RQData 如何用 Asyncio 来提高服务端性能,这次我们介绍一下 RQData 的通讯协议,通过对比 RQData 的通讯协议和一些常用程序的通讯协议,看看如何设计一个合适的通讯协议。
互联网上两台计算机进行通讯的基础是网络传输协议——数据在网络上以字节流的形式从一台计算机传输到另一台计算机,如果没有双方约定好的协议,就无法将数据流重塑成我们需要的数据结构。这就像是一个家长拆了一个用积木搭成的城堡,然后把一个一个积木递给小孩子,小孩子根据记忆用这些积木把城堡再重新搭起来。上述过程就是通讯协议涉及的内容,也是我们在讨论通讯协议所需要关心的事。
背景知识
讲解通讯协议之前,我们先介绍一下计算机是如何存储数据的。
计算机最小的存储单位是比特( bit ),一个比特有两种状态——1 和 0 。计算机里大部分部件是用晶体管组成的,晶体管有导通和截止两种状态,在数字电路里分别表示 1 和 0 。八个比特是一个字节( byte ),一个字节是大多数计算机语言能处理数据的最小单位 。1 个字节能表示的无符号整型范围是 0-255 ( 2 的 8 次方),如果大于 255 就需要用 2 个字节来表示,这时 2 个字节就有了先后排序的选择:假如 2 个字节的多位数中,低位放在较小的位置上,高位放在较大的位置上,称为小端排序,反之是大端排序。比如说 65280 需要用 11111111 和 00000000 表示,如果表示为 1111111100000000 就是小端排序,0000000011111111 就是大端排序。网络传输一般都是用大端排序,所以大端排序也被称为网络序。
程序中的对象需要转变为有意义的字节序列(类似于城堡到积木的过程)才能在网络中传输,之后字节序列被接收端反转成为一个相同的对象(类似于积木到城堡的过程),对象转为字节序列的这个过程称为序列化,也被称为编码,字节序列转变回对象的过程称为反序列化,也常被称为解码。
统一解决序列化 /反序列化以及数据传输问题的工具就是通讯协议。因此,我们在关注通讯协议的时候主要关注两个方面:
- 序列化协议是怎么做的;
- 在连接中,消息怎么传输的。
接下来我们就来看看几个常用的软件是怎么处理这两方面内容的。
Redis
Redis 是一个应用非常广泛的内存数据库,是现在最流行的键值对存储数据库之一。Redis 有 5 种基本的数据结构,分别是字符串、列表、哈希表、无序集合和有序集合。接下来我们从序列化协议和传输协议两方面来分析一下 Redis 是如何传输这些数据结构的。
序列化协议
Redis 的通讯协议设计上是可阅读的,它通过 CRLF (也就是 word 中的回车符,字符串表示为 “\r\n”
)来分隔不同的参数。Redis 的序列化协议是自己定制的,通过第一个字符来表明返回的类型,序列化的数据结构支持以下类型:
- 单行字符串:返回的第一个字节是 “+” ;
- 多行字符串:返回的第一个字节是 “$” ;
- 错误信息:返回的第一个字节是 “-” ;
- 整形数字:返回的第一个字节是“ : ” ;
- 数组:返回的第一个字节是“ * ”。
接下来具体介绍一下上述数据结构类型。
01 字符串
一个简单的字符串回复是 “+OK\r\n”
,单行字符串仅表示状态,比如说一个键值成功返回 OK 的设置就会使用单行字符串。如果字符串复杂或含有 \r\n 字符,Redis 则会通过多行字符串回复,一个多行字符串有如下结构 “ $6\r\nfoobar\r\n ”
,一个 $ 后面跟着一个数字来表示字符串的长度,数字和字符串的内容以 CRLF 分割,最后仍旧以一个 CRLF 结束。空字符串长度为 0,表示为 “$0\r\n\r\n”
。此外,当多行字符串长度为 -1,且只有一个 CRLF 时,特殊地表示为 NULL (无值),格式是 “$-1\r\n”
。
02 错误
如果出现错误,返回的第一个字符是 -, 接着的一行内容是错误的描述, 通常用空格区分错误类型和错误描述, 比如 “-WRONGTYPE Operation against a key holding the wrong kind of value”
, 表示请求的键值对应的类型不是期望的类型。
03 整形数字
返回类型是数字时,第一个字符是:
,以 CRLF 结尾,以字符串表示而非二进制。比如 “:0\r\n”
和“:512\r\n”
都是整数回复。整形 0 和 1 也被广泛地用于表示逻辑真和逻辑假,比如说判断一个键值存不存在,就通过返回“ 0 ”表示不存在,返回“ 1 ”表示存在。
04 数组
返回类型为数组时,第一个字符是 *
,紧接着是数组的长度 n,接下来就是 n 的具体内容 ——n 个上述提到的多个类型的数据。一个包含整形和字符串的 2 个元素的数组例子如下(多行结尾省略 CRLF )ÿ