前言
众所周知一个byte由8位bit组成,当我们表示一个状态时没有必要使用整个字节而是转为使用一个字节的一位或连续几位,这样做可以有效的减少协议头长度,提高传输效率。而如何灵活的使用bit表示状态,则需要对位操作比较熟悉。下面通过go语言rpcx协议的设计为示例进行介绍。rpcx使用的字节序是大端序(也是几乎所有网络协议的选择),如果对字节序不够了解的读者可以参考我的这篇博客
rpcx协议
rpcx 的 请求(request)和响应(response)使用相同的数据结构。
一个消息由下面的项组成:
项 | 大小 |
---|---|
Header | 4 字节 |
Message ID | 8 字节 |
total size | 4 字节, 不包含header和它本身, uint32类型 |
servicePath值的长度 | 4 字节, uint32类型 |
servicePath的值 | UTF-8 字符串 |
serviceMethod值的长度 | 4 字节, uint32类型 |
serviceMethod的值 | UTF-8 字符串 |
metadata的大小 | 4 字节, uint32类型 |
metadata: 格式 | size key1 string size value1 string, 可以包含多个 |
playload的大小 | 4 字节, uint32类型 |
playload的值 | slice of byte |
#4 + #6 + #8 + #10 + (4 + 4 + 4 + 4) 的字节数加起来等于 #3的值。
servicePath、serviceMethod、和meta中的key 、 value 都是UTF-8 字符串。
rpcx 使用 size of an element + element 的格式定义可变长的元素, 就像 TLV, 但是 rpcx 不需要 Type字段, 这是因为 元素的Type 要么是 UTF-8 字符串,要么就是明确的slice。
*对整数使用大端 BigEndian 编码 (integer type, int64, uint32 等等)
我们主要看Header部分的设计
Header在整个协议中一共占了四个字节,但是却表示了以下大量信息
- 1、T第一个字节是 0x08, 它是一个魔数 (magic number)
- 2、第二个字节是 version. 当前的版本是 0.
- 3、MessageType 可以是:
0: Request
1: Response - 4、Heartbeat: bool. 指示这个消息是否是heartbeat消息
- 5、Oneway: bool. true的话意味着服务不需要返回response
- 6、CompressType: 压缩类型
0: don’t compress
1: Gzip - 7、MessageStatusType: 指示 response 是一个错误还是正常的返回值
0: Normal
1: Error - 8、SerializeType: 编解码格式
0: 使用原始的byte slice
1: JSON
2: Protobuf
3: MessagePack
就是因为协议设计者对字节进行了拆分,使用自己的不同位数表示不同的信息,下面我们直接通过阅读源码的形式进行学习
rpcx header中的位操作
获取和设置指定位bit
先看Message的声明
type Message struct {
*Header
ServicePath string
ServiceMethod string
Metadata map[string]string
Payload []byte
data []byte
}
我们关注的重点就是这个Header,现在来看Header的声明
type Header [12]byte
可以看到Header就是一个字节数组,至于数组长度为什么不是4而是12应该是作者为了协议的扩展性考虑而进行的预留,实际代码中只使用到了前四个字节。
header中前两个信息项都是直接使用了一个字节,我们从第三项开始看起。
MessageType
// MessageType returns the message type.
func (h Header) MessageType() MessageType {
return MessageType(h[2]&0x80) >> 7
}<