当前位置: 首页 > news >正文

Linux基础组件之消息协议设计概述

消息协议设计

  • 通信协议设计核心
  • 协议设计细节
  • 协议设计目标
  • 协议概述
  • 消息的完整性判断
  • 协议设计
    • 示例1:即时通信的协议设计]
    • 示例2:云平台节点服务器
    • 示例3:nginx
    • 示例4:HTTP协议
    • 示例5:redis协议
  • 序列化方法
    • 常见序列化方法
    • 序列化结果数据对比
    • 序列化、反序列化速度对⽐
  • 数据安全
  • 数据压缩
  • 协议升级
  • 总结
  • 后言

通信协议设计核心

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

协议设计细节

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

协议设计目标

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

协议概述

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

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

消息的完整性判断

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

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

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

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

协议设计

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

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

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

字段类型⻓度(字节)说明
lengthunsigned int4整个消息的⻓度包括 协议头 + BODY
versionunsigned short2通信协议的版本号
appidunsigned short2对外SDK提供服务时,⽤来识别不同的客户
service_idunsigned short2对应命令的分组类⽐,⽐如login和msg是不同分组
command_idunsigned short2分组⾥⾯的⼦命令,⽐如login和login response
seq_numunsigned short2消息序号
reserveunsigned short2预留字节
bodyunsigned char[]n具体的协议数据

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

发送
接收端
根据消息类型确定反序列化类
反序列化body
接收数据
解析协议
发送端
封装到协议中
数据
序列化

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

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

字段类型⻓度(字节)说明
STAGunsigned short2通信协议数据包的开始标志 0xff 0xfe。 比如h264 0 0 0 1
versionunsigned short2通信协议的版本号
check_sumunsigned char1计算协议数据校验和,如果为加密数据,则计算密⽂校验 和。校验和计算范围:协议头CheckSum字段后数据,协议 体全部数据。
typeunsigned char10表示协议体是json格式,其它值未定义。设备⼼跳消息类型 的值为0xA0
seq_numunsigned int4通信数据报⽂的序列号,应答报⽂序列号必须与请求报⽂序 列号相同
lengthunsigned int4报⽂内容⻓度,即从该字段后报⽂内容⻓度
reserveunsigned int4预留字节
bodyunsigned char[]n具体数据

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

示例3:nginx

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;

示例4:HTTP协议

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

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

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

示例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:基本原理是,把内存中的数据直接输出,不做任何序列化操作。反序列化的时候,直接还 原内存。

常见序列化方法

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

类型通⽤性⼤⼩格式
XML通⽤重量级⽂本格式
JSON通⽤轻量级⽂本格式(⽅便调试)
Protobuf(编译器, ⽣成对应语⾔的代 码)独⽴轻量级⼆进制格式

序列化结果数据对比

XML:

<?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:

{
	"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:

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>是字段结束。

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

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

测试10W+。

序列化:

默认-O1序列化后字节
cJSON(C语⾔)488ms452ms297
jsoncpp(C++语⾔)871ms709ms255
rapidjson(C++语⾔)701ms113ms239
tinyxml2(XML)1383ms770ms474
protobuf241ms83ms117

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

反序列化:

默认-O1
cJSON284ms251ms
jsoncpp786ms709ms
rapidjson1288ms128ms
tinyxml21781ms953ms
protobuf190ms80ms

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

数据安全

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

数据压缩

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

协议升级

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

总结

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

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

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

后言

本专栏知识点是通过<零声教育>的系统学习,进行梳理总结写下文章,对c/c++linux系统提升感兴趣的读者,可以点击链接,详细查看详细的服务:C/C++服务器课程

相关文章:

  • [ vulhub漏洞复现篇 ] struts2远程代码执行漏洞 S2-005 (CVE-2010-1870)
  • 微服务Spring Boot 整合 Redis 分布式锁 Redission 实现优惠卷秒杀 一人一单
  • java毕业设计医护人员排班系统Mybatis+系统+数据库+调试部署
  • 分库分表实战之从根上带你吃透MySQL的索引
  • 学习大数据环境搭建
  • STM32——SD卡实验(SDIO方式)
  • JavaWeb(部分)
  • 还在盲目内卷?腾讯强推Spring Security 速成笔记,认证授权一键拿下
  • 【Vue】Vue中的侦听器watch
  • 合宙AIR32F103CBT6刷回CMSIS-DAP固件以及刷ST-LINK固件方法
  • 【C++修炼之路】4. 类和对象(中):日期类实现
  • 【百日刷题计划 第三天】——熟悉语法 语法基础题
  • 【Vue】初识Vue,Vue简介及Vue Devtools配置
  • 【云计算 | OpenStack】在无法网络访问的情况下,如何在KVM虚机和宿主机之间互传文件
  • java毕业设计演出票在线预定网站系统Mybatis+系统+数据库+调试部署
  • 【Redis学习笔记】2018-06-28 redis命令源码学习1
  • 【译】React性能工程(下) -- 深入研究React性能调试
  • 10个确保微服务与容器安全的最佳实践
  • 2019年如何成为全栈工程师?
  • Angular数据绑定机制
  • Druid 在有赞的实践
  • GDB 调试 Mysql 实战(三)优先队列排序算法中的行记录长度统计是怎么来的(上)...
  • MySQL常见的两种存储引擎:MyISAM与InnoDB的爱恨情仇
  • PHP 使用 Swoole - TaskWorker 实现异步操作 Mysql
  • python学习笔记 - ThreadLocal
  • 工作踩坑系列——https访问遇到“已阻止载入混合活动内容”
  • 前嗅ForeSpider中数据浏览界面介绍
  • 区块链共识机制优缺点对比都是什么
  • 如何利用MongoDB打造TOP榜小程序
  • 深入浅出webpack学习(1)--核心概念
  • 思维导图—你不知道的JavaScript中卷
  • 听说你叫Java(二)–Servlet请求
  • 为什么要用IPython/Jupyter?
  • 昨天1024程序员节,我故意写了个死循环~
  • ​​​​​​​GitLab 之 GitLab-Runner 安装,配置与问题汇总
  • #控制台大学课堂点名问题_课堂随机点名
  • ( )的作用是将计算机中的信息传送给用户,计算机应用基础 吉大15春学期《计算机应用基础》在线作业二及答案...
  • (51单片机)第五章-A/D和D/A工作原理-A/D
  • (6)【Python/机器学习/深度学习】Machine-Learning模型与算法应用—使用Adaboost建模及工作环境下的数据分析整理
  • (二)丶RabbitMQ的六大核心
  • (附表设计)不是我吹!超级全面的权限系统设计方案面世了
  • (力扣记录)1448. 统计二叉树中好节点的数目
  • (强烈推荐)移动端音视频从零到上手(下)
  • (十八)SpringBoot之发送QQ邮件
  • (转)可以带来幸福的一本书
  • (转)微软牛津计划介绍——屌爆了的自然数据处理解决方案(人脸/语音识别,计算机视觉与语言理解)...
  • (转载)PyTorch代码规范最佳实践和样式指南
  • .NET WebClient 类下载部分文件会错误?可能是解压缩的锅
  • .Net的DataSet直接与SQL2005交互
  • /bin、/sbin、/usr/bin、/usr/sbin
  • @Controller和@RestController的区别?
  • @Tag和@Operation标签失效问题。SpringDoc 2.2.0(OpenApi 3)和Spring Boot 3.1.1集成
  • [2019/05/17]解决springboot测试List接口时JSON传参异常
  • [2021 蓝帽杯] One Pointer PHP
  • [ACM] hdu 1201 18岁生日