首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >掌握消息协议设计的艺术:释放高效通信的力量

掌握消息协议设计的艺术:释放高效通信的力量

原创
作者头像
Lion 莱恩呀
发布2024-11-02 20:29:02
发布2024-11-02 20:29:02
3280
举报
文章被收录于专栏:后端开发技术后端开发技术

一、协议设计概述

1.1、 通信协议设计核心

(1)解析效率。 (2)可扩展、可升级。

1.2、协议设计细节

(1)数据帧的完整性判断。 (2)序列化和反序列化。 (3)协议升级,兼容性。 (4)协议安全。 (5)数据压缩。

1.3、协议设计目标

(1)解析效率:高并发场景下,解析效率决定了使用协议的CPU成本。 (2)编码长度:决定了使用协议的网络带宽和存储成本。 (3)易于实现:满足需求的协议就是好协议,不追求大而全的。 (4)可读性:决定了使用协议的调试和维护成本。 (5)兼容性:协议可能会经常升级,使⽤协议的双⽅是否可以独⽴升级协 议、增减协议中的字段⾮常重要。 (6)跨平台语言:协议适用于任何语言来实现。⽐如Windows⽤C++,Android⽤Java, Web⽤Js,IOS⽤object-c。 (7)安全可靠:防止数据被破解。

1.4、协议概述

协议是⼀种约定,通过约定,不同的进程可以对⼀段数据产⽣相同的理解,从⽽可以相互协 作,存在进程间通信的程序就⼀定需要协议。

⽐如不同表的插头,还需要进⾏各种转换,如果我们两端进⾏通信没有约定好协议,那彼此是不知道对⽅ 发送的数据是什么意义。

二、协议设计

(1)消息边界。使用什么方式界定消息边界。 (2)版本区分。版本号放在何处合适。 (3)消息类型区分。对应不同的业务。

协议设计不是为了通用,主要是为了适合业务,避免臃肿。

2.1、消息的完整性判断

为了能让对端知道如何给消息帧分界,目前一般有一下做法: (1)固定大小。不推荐。 以固定⼤⼩字节数⽬来分界,如每个消息100个字节(不足100就填充,超过100就分包),对端每收⻬100个字节,就当成⼀个消息来解析。

(2)以特定符号分界。 如每个消息都以特定的字符来结尾(如\r\n),当在字节流中读取到该字符时, 则表明上⼀个消息到此为⽌。HTTP就是以特定符号分界。

(3)固定消息头+消息体结构。推荐。 这种结构中⼀般消息头部分是⼀个固定字节⻓度的结构,并且消息头中会有 ⼀个特定的字段指定消息体的⼤⼩。收消息时,先接收固定字节数的头部,解出这个消息完整⻓度, 按此⻓度接收消息体。这是⽬前各种⽹络应⽤⽤的最多的⼀种消息格式;header + body。

(4)特殊字符+消息长度+分隔符。 在序列化后的buffer前⾯增加⼀个字符流的头部,其中有个字段存储消息总⻓度,根据特殊字符(⽐ 如根据\n或者\0)判断头部的完整性。这样通常⽐3要麻烦⼀些,HTTP和REDIS采⽤的是这种⽅式。收消息的时候,先判断已收到的数据中是否包含结束符,收到结束符后解析消息头,解出这个消息完 整⻓度,按此⻓度接收消息体。

2.2、示例1:即时通信的协议设计

字段

类型

⻓度(字节)

说明

length

unsigned int

4

整个消息的⻓度包括 协议头 + BODY

version

unsigned short

2

通信协议的版本号

appid

unsigned short

2

对外SDK提供服务时,⽤来识别不同的客户

service_id

unsigned short

2

对应命令的分组类⽐,⽐如login和msg是不同分组

command_id

unsigned short

2

分组⾥⾯的⼦命令,⽐如login和login response

seq_num

unsigned short

2

消息序号

reserve

unsigned short

2

预留字节

body

unsigned char[]

n

具体的协议数据

注意: (1)length一定要约定好是body的长度还是header+body的长度。 (2)版本号尽量靠前,是为了版本升级的便携性,反正不同版本的后续字段不同导致的未知问题。 (3)内部有不同业务,可以考虑使用appid来做识别。 (4)消息类型的识别。比如登录业务和消息聊天业务,登录有登录请求和响应等,消息聊天又有私聊和群聊等。

(5)消息序列号主要用来业务的应答。判断消息是否已被接收处理成功,要不要重发等。TCP数据传输可靠不代表业务可靠。 (6)一般来说,设计协议的时候要留一些预留位,为了后期有变动或扩展时能兼容。

2.3、示例2:云平台节点服务器

字段

类型

⻓度(字节)

说明

STAG

unsigned short

2

通信协议数据包的开始标志 0xff 0xfe。比如h264 0 0 0 1

version

unsigned short

2

通信协议的版本号

check_sum

unsigned char

1

计算协议数据校验和,如果为加密数据,则计算密⽂校验 和。校验和计算范围:协议头CheckSum字段后数据,协议 体全部数据。

type

unsigned char

1

0表示协议体是json格式,其它值未定义。设备⼼跳消息类型 的值为0xA0

seq_num

unsigned int

4

通信数据报⽂的序列号,应答报⽂序列号必须与请求报⽂序 列号相同

length

unsigned int

4

报⽂内容⻓度,即从该字段后报⽂内容⻓度

reserve

unsigned int

4

预留字节

body

unsigned char[]

n

具体数据

注意:这里有一个STAG用于标志数据包的开始,其他和上面的含义类似。

2.4、示例3:nginx

代码语言:javascript
复制
typedef struct{
	ngx_char_t		magic[2];	//magic number
	ngx_short_t		version;	// protocol version
	ngx_short_t		type;		// protocol type: json、xml、binary、....
	ngx_short_t		len;		// body length
	ngx_uint_t		seq;		// message number
	ngx_short_t		id;			// message id
	ngx_char_t		reserve[2];	// reserve
} ngx_message_head_t;

2.5、示例4:HTTP协议

HTTP协议是最常⻅的协议。但是这个⼀般是不适合采⽤HTTP协议作为互联⽹后台的协议,主要是考虑到以下2个原因: (1) HTTP协议只是⼀个框架,没有指定包体的序列化⽅式,所以还需要配合其他序列化的⽅式使⽤才能传 递业务逻辑数据。 (2)HTTP协议解析效率低,⽽且⽐较复杂(不知道有没有⼈觉得HTTP协议简单,其实不是http协议简单, ⽽是HTTP⼤家⽐较熟悉⽽已) 。

有些情况下是可以使⽤HTTP协议的: (1)对公⽹⽤户api,HTTP协议的穿透性最好,所以最适合; (2)效率要求没那么⾼的场景; (3) 希望提供更多⼈熟悉的接口,⽐如新浪微、腾讯博提供的开放接⼝。

HTTP的body是文本还是二进制? 这依赖于是否压缩,如果没有压缩就是文本;如果压缩了就是二进制,需要客户端解压成文本;如果传输的是视频流或图片,那么body就是二进制的。头部一定是文本的。

2.6、示例5:redis协议

基本原理是:先发送⼀个字符串表示参数个数,然后再逐个发送参数,每个参数发送的时候,先发送⼀个 字符串表示参数的数据⻓度,再发送参数的内容。

在redis 中, ⼀些数据的类型通过它的第⼀个字节进⾏判断: (1)单⾏(Simple Strings)回复:回复的第⼀个字节是 “+” 。 (2)错误(Errors)信息:回复的第⼀个字节是 “-” 。 (3)整形数字(Integers):回复的第⼀个字节是 “:” 。 (4)多⾏字符串(Bulk Strings):回复的第⼀个字节是 “$” 。 (5)数组(Arrays):回复的第⼀个字节是 “*”。

此外,redis能够使⽤稍后指定的Bulk Strings或Array的特殊变体来表示Null值。在redis中,协议的不 同部分始终以“\r\n”(CRLF)结束。

三、序列化方法

(1)TVL编码及其变体(TVL是tag,length和value的缩写):比如protobuf。 (2)文本流编码:比如xml、json。 (3)固定结构编码:基本原理是,协议约定了传输字段类型和字段含义,和TLV的⽅式类似,但是没有了 tag和len,只有value,⽐如TCP/IP。 (4)内存dump:基本原理是,把内存中的数据直接输出,不做任何序列化操作。反序列化的时候,直接还 原内存。

3.1、常见序列化方法

主流序列化协议:xml,json,protobuf。 (1)XML指可扩展标记语⾔(eXtensible Markup Language)。是⼀种通⽤和重量级的数据交换格式。以⽂本⽅式存储。 (2) JSON(JavaScript ObjectNotation, JS 对象简谱) 是⼀种通⽤和轻量级的数据交换格式。以⽂本结构 进⾏存储。 (3)protocol buffer是Google的⼀种独⽴和轻量级的数据交换格式。以⼆进制结构进⾏存储。

类型

通⽤性

⼤⼩

格式

XML

通⽤

重量级

⽂本格式

JSON

通⽤

轻量级

⽂本格式(⽅便调试)

Protobuf(编译器, ⽣成对应语⾔的代 码)

独⽴

轻量级

⼆进制格式

3.2、序列化结果数据对比

XML:

代码语言:javascript
复制
<?xml version="1.0" encoding="utf-8" ?>
<?xml-stylesheet type="text/css" href="test.css"?> 
<test>
	<name>HelloWorld</name>
	<sex>male</sex>
	<birthday>7.1</birthday>
	<skill>AI</skill>
</test>

JSON:

代码语言:javascript
复制
{
	"name": "HelloWorld",
	"age": 80,
	"languages": ["C","linux","C++"],
	"phone": {
		"number": "12345678901",
		"type": "home"
	},
	"china": true,
	"books":[
		{
			"name": "Linux c development",
			"price": 18.8
		},
		{
			"name": "Linux server development",
			"price": 188.8
		}
	],
}

protobuf:

代码语言:javascript
复制
16 36 16 36 00 00 16 36 16 36 16 36 16 36 00 00 sf
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 sf
00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 sf
00 00 00 00 00 00 9C 00 00 00 00 00 E7 00 36 76 sf
11 11 40

序列化方法对每个字段有边界的约束。比如xml中的< name>是字段开始,< /name>是字段结束。

为什么需要序列化?因为字段值是变长的,需要一个方法约束起始和接收的边界。

3.3、序列化、反序列化速度对⽐

测试10W+。

序列化:

默认

-O1

序列化后字节

cJSON(C语⾔)

488ms

452ms

297

jsoncpp(C++语⾔)

871ms

709ms

255

rapidjson(C++语⾔)

701ms

113ms

239

tinyxml2(XML)

1383ms

770ms

474

protobuf

241ms

83ms

117

可以看到,同样是json,为什么序列化后数据大小不一样?这是由于排版的问题,比如不换行不缩进,紧凑占用的字节就少了。

反序列化:

默认

-O1

cJSON

284ms

251ms

jsoncpp

786ms

709ms

rapidjson

1288ms

128ms

tinyxml2

1781ms

953ms

protobuf

190ms

80ms

和序列化的速度差不多的。

四、数据传输

4.1、数据安全

数据加密。 (1)AES (2)openssl (3)Signal protocol端到端的通讯加密协议。

4.2、数据压缩

文本情况下压缩,二进制压缩没有太多意义。 (1)defate (2)gzip (3)lzw

4.3、协议升级

协议升级:增加字段。 (1)通过版本号指明协议版本,即是通过版本号辨别不同类型的协议 。 (2) ⽀持协议头部可扩展,即是在设计协议头部的时候有⼀个字段⽤来指明头部的⻓度。

五、总结

通信协议设计的核心目标是为了解析效率、可扩展、可升级;高并发下的通信协议应该高解析效率、易于实现、兼容性强、跨语言、安全可靠。

消息帧的完整性判断方式有:固定长度(不推荐)、header+body(推荐)、以特定符号分界、特殊字符+消息长度+分隔符。

序列化方法有:TVB编码及变体、文本流编码、固定结构编码、内存dump。 主流序列化协议有XML、JSON、protobuf。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 一、协议设计概述
    • 1.1、 通信协议设计核心
    • 1.2、协议设计细节
    • 1.3、协议设计目标
    • 1.4、协议概述
  • 二、协议设计
    • 2.1、消息的完整性判断
    • 2.2、示例1:即时通信的协议设计
    • 2.3、示例2:云平台节点服务器
    • 2.4、示例3:nginx
    • 2.5、示例4:HTTP协议
    • 2.6、示例5:redis协议
  • 三、序列化方法
    • 3.1、常见序列化方法
    • 3.2、序列化结果数据对比
    • 3.3、序列化、反序列化速度对⽐
  • 四、数据传输
    • 4.1、数据安全
    • 4.2、数据压缩
    • 4.3、协议升级
  • 五、总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档