tunm二进制协议在python上的实现
tunm二进制协议在python上的实现
tunm是一种对标JSON的二进制协议, 支持JSON的所有类型的动态组合
支持的数据类型
基本支持的类型 "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "varint", "float", "string", "raw", "array", "map"
为什么我们需要二进制协议
下图是文本格式JSON与tunm的对比
| 类型 | 可读 | 可编辑 | 编码速度 | 解码速度 | 数据大小 | 预定义 |
|---|---|---|---|---|---|---|
| JSON | ✓ | ✓ | 慢 | 慢 | 大 | 否 |
| tunm | x | x | 快 | 快 | 小 | 否 |
| protobuf | x | x | 快 | 快 | 小 | 是 |
在高性能的场景下, 或者需要流量传输比较敏感的地方, 通常会选择二进制来代替文本协议来做为通讯的, 如RPC, REST, 游戏等情况。
相对于google protobuf, 它需要比较完善的预定义过程, 就比如客户端版本1, 服务端版本2, 就有比较大的可能造成不兼容, 对需求经常变化的就会比较难与同步。
tunm相对于JSON, 若第一版是
{
"name": "tunm", "version": 1
}
此时第二版需要加入用户的id, 就可以很方便的变成
{
"name": "tunm", "version": 2, "id": 1
}
而对客户端1来说, 只是多一个id的字段, 不会有任何的破坏, 做到版本升级而无影响
协议的二进制格式
数据协议分为三部分(协议名称, 字符串索引区, 数据区(默认为数组))
如数据协议名为cmd_test_op, 数据为["tunm_proto", {"name": "tunm_proto", "tunm_proto": 1}]
- 那么数据将先压缩协议名cmd_test_op, 将先写下可变长度(varint)值为11占用1字节, 然后再写入cmd_test_op的utf8的字节数
- 接下来准备写入字符串索引区, 索引数据用到的字符串为["tunm_proto", "name"]两个字符串, 即将写入可变长度(varint)值为2占用一字节, 然后分别写入字符串tunm_proto和name两个字符串, 这样子字符串相接近有利于压缩, 且如果有相同的字符串可以更好的进行复用
- 接下来准备写入数据区,
首先判断为一个数组, 写入类型u8(TYPE_ARR=16), 写入数组长度varint(2), 准备开始写第一个数据, 字符串tunm_proto, 已转成id, 则写入类型u8(TYPE_STR_IDX=14), 查索引号0, 则写入varint(0), 第一个字段写入完毕, 接下来第二个字段是一个map数据, 写入map长度varint(2), 然后进行遍历得到key值为name, 则写入写入类型u8(TYPE_STR_IDX=14),查索引号1, 则写入varint(1), 然后开始写name对应的值tunm_proto, 写入TYPE_STR_IDX类型的0值, 则这组key写入完毕, 依此类推写入第二组数据
协议的实现(小端对齐)
ByteBuffer的实现
ByteBuffer具有组装字节流的功能, 比如写入字符串, 写入int, 还有里面存储字符串索引区
class ByteBuffer(object):
def __init__(self):
# 字节缓冲区
self.buffer = bytearray([00]*1024)
# 写入的位置索引号
self.wpos = 0
# 读出的位置索引号
self.rpos = 0
# 大小端格式
self.endianness = "little"
# 索引的数组及快速查询的字符串索引号
self.str_arr = []
self.str_map = {}
类型的定义
@enum.unique
class TP_DATA_TYPE(IntEnum):
TYPE_NIL = 0,
TYPE_BOOL = 1,
TYPE_U8 = 2,
TYPE_I8 = 3,
TYPE_U16 = 4,
TYPE_I16 = 5,
TYPE_U32 = 6,
TYPE_I32 = 7,
TYPE_U64 = 8,
TYPE_I64 = 9,
TYPE_VARINT = 10,
TYPE_FLOAT = 11,
TYPE_DOUBLE = 12,
TYPE_STR = 13,
TYPE_STR_IDX = 14,
TYPE_RAW = 15,
TYPE_ARR = 16,
TYPE_MAP = 17,
数据的组装
变长的int类型, 用来写入string长度, 数组长度, map长度, 部分数值类型
@staticmethod
def encode_varint(buffer: ByteBuffer, value):
'''
如果原数值是正数则将原数值变成value*2
如果原数值是负数则将原数值变成-(value + 1) * 2 + 1
相当于0->0, -1->1, 1->2,-2->3,2->4来做处理
因为小数值是常用的, 所以保证小数值及负数的小数值尽可能的占少位
'''
if type(value) == bool:
value = 1 if value else 0
real = value * 2
if value < 0:
real = -(value + 1) * 2 + 1
for _i in range(12):
# 每个字节的最高位来表示有没有下一位, 若最高位为0, 则已完毕
b = real & 0x7F
real >>= 7
if real > 0:
buffer.write_u8(b | 0x80)
else:
buffer.write_u8(b)
break
写入字符串, 把字符串变成索引值, 如果协议里有大量重复的字符串可大大的节约协议的长度
@staticmethod
def encode_str_idx(buffer: ByteBuffer, value):
'''
写入字符串索引值, 在数值区里的所有字符串默认会被写成索引值
如果重复的字符串则会返回相同的索引值(varint)
'''
idx = buffer.add_str(value)
TPPacker.encode_type(buffer, TP_DATA_TYPE.TYPE_STR_IDX)
TPPacker.encode_varint(buffer, idx)
写入各种对应的类型
@staticmethod
def encode_field(buffer: ByteBuffer, value, pattern=None):
'''
先写入类型的值(u8), 则根据类型写入类型对应的的数据
'''
if not pattern:
pattern = TPPacker.get_type_by_ref(value)
if pattern == TP_DATA_TYPE.TYPE_NIL:
return None
elif pattern == TP_DATA_TYPE.TYPE_BOOL:
TPPacker.encode_type(buffer, pattern)
TPPacker.encode_bool(buffer, value)
elif pattern >= TP_DATA_TYPE.TYPE_U8 and pattern <= TP_DATA_TYPE.TYPE_I8:
TPPacker.encode_type(buffer, pattern)
TPPacker.encode_number(buffer, value, pattern)
elif pattern >= TP_DATA_TYPE.TYPE_U16 and pattern <= TP_DATA_TYPE.TYPE_I64:
TPPacker.encode_type(buffer, TP_DATA_TYPE.TYPE_VARINT)
TPPacker.encode_varint(buffer, value)
elif pattern == TP_DATA_TYPE.TYPE_FLOAT:
TPPacker.encode_type(buffer, pattern)
TPPacker.encode_number(buffer, value, pattern)
elif pattern == TP_DATA_TYPE.TYPE_DOUBLE:
TPPacker.encode_type(buffer, pattern)
TPPacker.encode_number(buffer, value, pattern)
elif pattern == TP_DATA_TYPE.TYPE_STR:
TPPacker.encode_str_idx(buffer, value)
elif pattern == TP_DATA_TYPE.TYPE_RAW:
TPPacker.encode_type(buffer, pattern)
TPPacker.encode_str_raw(buffer, value)
elif pattern == TP_DATA_TYPE.TYPE_ARR:
TPPacker.encode_type(buffer, pattern)
TPPacker.encode_arr(buffer, value)
elif pattern == TP_DATA_TYPE.TYPE_MAP:
TPPacker.encode_type(buffer, pattern)
TPPacker.encode_map(buffer, value)
else:
raise Exception("unknow type")
@staticmethod
def encode_arr(buffer: ByteBuffer, value):
'''
写入数组的长度, 再写入各各元素的值
'''
TPPacker.encode_varint(buffer, len(value))
for v in value:
TPPacker.encode_field(buffer, v)
@staticmethod
def encode_map(buffer: ByteBuffer, value):
'''
写入map的长度, 再分别写入map各元素的key, value值
'''
TPPacker.encode_varint(buffer, len(value))
for k in value:
TPPacker.encode_field(buffer, k)
TPPacker.encode_field(buffer, value[k])
写入一条协议
@staticmethod
def encode_proto(buffer: ByteBuffer, name, infos):
'''
写入协议名称, 然后写入字符串索引区(即字符串数组), 然后再写入协议的详细数据
'''
sub_buffer = ByteBuffer()
TPPacker.encode_field(sub_buffer, infos)
TPPacker.encode_str_raw(buffer, name, TP_DATA_TYPE.TYPE_STR)
TPPacker.encode_varint(buffer, len(sub_buffer.str_arr))
for val in sub_buffer.str_arr:
TPPacker.encode_str_raw(buffer, val, TP_DATA_TYPE.TYPE_STR)
buffer.write_bytes(sub_buffer.all_bytes())
解码与编码的过程相反, 类似的过程
相关连接
协议地址https://github.com/tickbh/TunmProto
tunm二进制协议在python上的实现的更多相关文章
- REST RPC HTTP vs 高性能二进制协议 序列化和通信协议
edisonchou https://mp.weixin.qq.com/s/-XZXqXawR-NxJMPCeiNsmg .NET Core微服务之服务间的调用方式(REST and RPC) Edi ...
- Thrift的TBinaryProtocol二进制协议分析
先上张图,说明一下thrift的二进制协议是什么东东. 报文格式编码: bool类型: 一个字节的类型,两个字节的字段编号,一个字节的值(true:1,false:0). Byte类型: 一个字节的类 ...
- 轻量级通信引擎StriveEngine —— C/S通信demo(2) —— 使用二进制协议 (附源码)
在网络上,交互的双方基于TCP或UDP进行通信,通信协议的格式通常分为两类:文本消息.二进制消息. 文本协议相对简单,通常使用一个特殊的标记符作为一个消息的结束. 二进制协议,通常是由消息头(Head ...
- HTTP与私有二进制协议之间的区别
简单的文本协议.二进制协议 写网络程序躲不过协议,协议其实就是定义了消息的格式,以及消息是如何交换的.协议可简单可复杂,复杂精密如TCP协议,简单奔放如HTTP的协议.这里将我所接触到的协议稍微总结一 ...
- C#轻量级通通讯组件StriveEngine —— C/S通信开源demo(2) —— 使用二进制协议 (附源码)
前段时间,有几个研究ESFramework通信框架的朋友对我说,ESFramework有点庞大,对于他们目前的项目来说有点“杀鸡用牛刀”的意思,因为他们的项目不需要文件传送.不需要P2P.不存在好友关 ...
- arp协议分析&python编程实现arp欺骗抓图片
arp协议分析&python编程实现arp欺骗抓图片 序 学校tcp/ip协议分析课程老师布置的任务,要求分析一种网络协议并且研究安全问题并编程实现,于是我选择了研究arp协议,并且利用pyt ...
- 二进制协议gob及msgpack介绍
本文主要介绍二进制协议gob及msgpack的基本使用. 最近在写一个gin框架的session服务时遇到了一个问题,Go语言中的json包在序列化空接口存放的数字类型(整型.浮点型等)都序列化成fl ...
- SSL/TLS协议详解(上):密码套件,哈希,加密,密钥交换算法
本文转载自SSL/TLS协议详解(上):密码套件,哈希,加密,密钥交换算法 导语 作为一名安全爱好者,我一向很喜欢SSL(目前是TLS)的运作原理.理解这个复杂协议的基本原理花了我好几天的时间,但只要 ...
- HTTP协议一次上传多个文件的方法
如何通过HTTP协议一次上传多个文件呢?在这里有两个思路,是同一个方法的两种实现.具体程序还需自己去设计 1. 在form中设置多个文件输入框,用数组命名他们的名字,如下: < form act ...
- 用c++开发基于tcp协议的文件上传功能
用c++开发基于tcp协议的文件上传功能 2005我正在一家游戏公司做程序员,当时一直在看<Windows网络编程> 这本书,把里面提到的每种IO模型都试了一次,强烈推荐学习网络编程的同学 ...
随机推荐
- 【java-Err】 Generating equals/hashCode implementation but without a call to superclass,
@EqualsAndHashCode 默认不继承父类 修复此问题的方法很简单: 1. 使用@Getter @Setter @ToString代替@Data并且自定义equals(Object ot ...
- langchain:Prompt在手,天下我有
目录 简介 好的prompt 什么是prompt template 在langchain中创建prompt template Chat特有的prompt template 总结 简介 prompts是 ...
- 6月有奖征文挑战,ZEGO开发者社区首季活动报名入口!
前 言 哈喽 开发者们: ZEGO即构科技作为一家20年技术积累的音视频云服务商,已经为全球200+个国家的企业服务,单日通话时长突破30亿+分钟,现下即构开发者社区举办首期征文活动!本次征文活动围绕 ...
- 即构SDK8月迭代:新增下行网络测速、切换房间、预览首帧回调等多项功能
即构SDK8月上新,网络探测模块新增下行网络测速.媒体播放器新增缓存到文件结尾的回调.新增切换房间功能.新增预览首帧回调等.本月SDK迭代新上线的功能非常丰富,详细内容请看下文. LiveRoom 新 ...
- Matlab背景颜色修改
背景 将修改内容添加到matlab的matlab.prf文件中,文件路径为在matlab中运行prefdir的结果,直接添加这些内容保存就好. github主题制作地址,里面有多种matlab主题配色 ...
- maven项目创建后添加resources等文件夹
maven项目初始化只生成src/main/resources目录,但是这个不够用,我们得创建 src/main/java目录 src/test/java目录 src/test/resources目录 ...
- MiniNK WEB 选拔题 by F12
Start 除了梦想外一无所有的我们,将会和蔑视与困境做最后的斗争,这是最后一舞 N0wayBack 联合战队成立以来一直致力于信息安全技术的研究,作为联合战队活跃在各大 CTF (信息安全竞赛)赛事 ...
- 一步一图带你构建 Linux 页表体系 —— 详解虚拟内存如何与物理内存进行映射
笔者之前在自己的专栏<聊聊 Linux 内核> 里通过大量的篇幅写了一个系列关于内存管理相关的文章,在这个系列文章中,笔者分别通过虚拟内存管理和物理内存管理两个角度算是把 Linux 内存 ...
- 【译】Visual Studio 2022 中的 Web API 开发
在 Visual Studio 2022 中,Web 开发人员的主要场景之一是使用 ASP.NET Core 创建 Web API.在 Visual Studio 2022 17.6 的最新预览版中, ...
- C++欧几里得算法求最大公约数和最小公倍数
定义 最大公约数即为 Greatest Common Divisor,常缩写为 gcd. 一组整数的公约数,是指同时是这组数中每一个数的约数的数. 一组整数的最大公约数,是指所有公约数里面最大的一个. ...