| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> Python知识库 -> tunm二进制协议在python上的实现 -> 正文阅读 |
|
[Python知识库]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, 若第一版是 ```json { ? ? "name": "tunm", "version": 1 } ``` 此时第二版需要加入用户的id, 就可以很方便的变成 ```json { ? ? "name": "tunm", "version": 2, "id": 1 } ``` 而对客户端1来说, 只是多一个id的字段, 不会有任何的破坏, 做到版本升级而无影响 ## 协议的二进制格式 数据协议分为三部分(协议名称, 字符串索引区, 数据区(默认为数组)) 如数据协议名为cmd_test_op, 数据为["tunm_proto", {"name": "tunm_proto", "tunm_proto": 1}] 1. 那么数据将先压缩协议名cmd_test_op, 将先写下可变长度(varint)值为11占用1字节, 然后再写入cmd_test_op的utf8的字节数 2. 接下来准备写入字符串索引区, 索引数据用到的字符串为["tunm_proto", "name"]两个字符串, 即将写入可变长度(varint)值为2占用一字节, 然后分别写入字符串tunm_proto和name两个字符串, 这样子字符串相接近有利于压缩, 且如果有相同的字符串可以更好的进行复用 3. 接下来准备写入数据区, 首先判断为一个数组, 写入类型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, 还有里面存储字符串索引区 ```python 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 = {} ``` [ByteBuffer源码地址](https://github.com/tickbh/TunmProto/blob/main/python/tunm_proto/bytebuffer.py) ### 类型的定义 ```python @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长度, 部分数值类型 ```python @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 ``` ##### 写入字符串, 把字符串变成索引值, 如果协议里有大量重复的字符串可大大的节约协议的长度 ```python @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) ``` ##### 写入各种对应的类型 ```python @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]) ``` ##### 写入一条协议 ```python @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()) ``` ##### 解码与编码的过程相反, 类似的过程 [tunm源码地址](https://github.com/tickbh/TunmProto/blob/main/python/tunm_proto/tunm.py)
#### 相关连接 [协议地址https://github.com/tickbh/TunmProto](https://github.com/tickbh/TunmProto) |
|
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年12日历 | -2024/12/25 2:11:00- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |
数据统计 |