真实的国产乱ⅩXXX66竹夫人,五月香六月婷婷激情综合,亚洲日本VA一区二区三区,亚洲精品一区二区三区麻豆

成都創(chuàng)新互聯(lián)網(wǎng)站制作重慶分公司

tunm二進(jìn)制協(xié)議在python上的實(shí)現(xiàn)

tunm二進(jìn)制協(xié)議在python上的實(shí)現(xiàn)

tunm是一種對(duì)標(biāo)JSON的二進(jìn)制協(xié)議, 支持JSON的所有類型的動(dòng)態(tài)組合

創(chuàng)新互聯(lián)公司自2013年創(chuàng)立以來,先為慶安等服務(wù)建站,慶安等地企業(yè),進(jìn)行企業(yè)商務(wù)咨詢服務(wù)。為慶安企業(yè)網(wǎng)站制作PC+手機(jī)+微官網(wǎng)三網(wǎng)同步一站式服務(wù)解決您的所有建站問題。

支持的數(shù)據(jù)類型

基本支持的類型 "u8", "i8", "u16", "i16", "u32", "i32", "u64", "i64", "varint", "float", "string", "raw", "array", "map"

為什么我們需要二進(jìn)制協(xié)議

下圖是文本格式JSON與tunm的對(duì)比

類型 可讀 可編輯 編碼速度 解碼速度 數(shù)據(jù)大小 預(yù)定義
JSON ? ?
tunm x x
protobuf x x

在高性能的場(chǎng)景下, 或者需要流量傳輸比較敏感的地方, 通常會(huì)選擇二進(jìn)制來代替文本協(xié)議來做為通訊的, 如RPC, REST, 游戲等情況。
相對(duì)于google protobuf, 它需要比較完善的預(yù)定義過程, 就比如客戶端版本1, 服務(wù)端版本2, 就有比較大的可能造成不兼容, 對(duì)需求經(jīng)常變化的就會(huì)比較難與同步。
tunm相對(duì)于JSON, 若第一版是

{
    "name": "tunm", "version": 1
}

此時(shí)第二版需要加入用戶的id, 就可以很方便的變成

{
    "name": "tunm", "version": 2, "id": 1
}

而對(duì)客戶端1來說, 只是多一個(gè)id的字段, 不會(huì)有任何的破壞, 做到版本升級(jí)而無影響

協(xié)議的二進(jìn)制格式

數(shù)據(jù)協(xié)議分為三部分(協(xié)議名稱, 字符串索引區(qū), 數(shù)據(jù)區(qū)(默認(rèn)為數(shù)組))
如數(shù)據(jù)協(xié)議名為cmd_test_op, 數(shù)據(jù)為["tunm_proto", {"name": "tunm_proto", "tunm_proto": 1}]

  1. 那么數(shù)據(jù)將先壓縮協(xié)議名cmd_test_op, 將先寫下可變長(zhǎng)度(varint)值為11占用1字節(jié), 然后再寫入cmd_test_op的utf8的字節(jié)數(shù)
  2. 接下來準(zhǔn)備寫入字符串索引區(qū), 索引數(shù)據(jù)用到的字符串為["tunm_proto", "name"]兩個(gè)字符串, 即將寫入可變長(zhǎng)度(varint)值為2占用一字節(jié), 然后分別寫入字符串tunm_proto和name兩個(gè)字符串, 這樣子字符串相接近有利于壓縮, 且如果有相同的字符串可以更好的進(jìn)行復(fù)用
  3. 接下來準(zhǔn)備寫入數(shù)據(jù)區(qū),
    首先判斷為一個(gè)數(shù)組, 寫入類型u8(TYPE_ARR=16), 寫入數(shù)組長(zhǎng)度varint(2), 準(zhǔn)備開始寫第一個(gè)數(shù)據(jù), 字符串tunm_proto, 已轉(zhuǎn)成id, 則寫入類型u8(TYPE_STR_IDX=14), 查索引號(hào)0, 則寫入varint(0), 第一個(gè)字段寫入完畢, 接下來第二個(gè)字段是一個(gè)map數(shù)據(jù), 寫入map長(zhǎng)度varint(2), 然后進(jìn)行遍歷得到key值為name, 則寫入寫入類型u8(TYPE_STR_IDX=14),查索引號(hào)1, 則寫入varint(1), 然后開始寫name對(duì)應(yīng)的值tunm_proto, 寫入TYPE_STR_IDX類型的0值, 則這組key寫入完畢, 依此類推寫入第二組數(shù)據(jù)

協(xié)議的實(shí)現(xiàn)(小端對(duì)齊)

ByteBuffer的實(shí)現(xiàn)

ByteBuffer具有組裝字節(jié)流的功能, 比如寫入字符串, 寫入int, 還有里面存儲(chǔ)字符串索引區(qū)

class ByteBuffer(object):
    def __init__(self):
        # 字節(jié)緩沖區(qū)
        self.buffer = bytearray([00]*1024)
        # 寫入的位置索引號(hào)
        self.wpos = 0
        # 讀出的位置索引號(hào)
        self.rpos = 0
        # 大小端格式
        self.endianness = "little"
        # 索引的數(shù)組及快速查詢的字符串索引號(hào)
        self.str_arr = []
        self.str_map = {}

ByteBuffer源碼地址

類型的定義

@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,

數(shù)據(jù)的組裝

變長(zhǎng)的int類型, 用來寫入string長(zhǎng)度, 數(shù)組長(zhǎng)度, map長(zhǎng)度, 部分?jǐn)?shù)值類型
@staticmethod
def encode_varint(buffer: ByteBuffer, value):
    '''
    如果原數(shù)值是正數(shù)則將原數(shù)值變成value*2
    如果原數(shù)值是負(fù)數(shù)則將原數(shù)值變成-(value + 1) * 2 + 1
    相當(dāng)于0->0, -1->1, 1->2,-2->3,2->4來做處理
    因?yàn)樾?shù)值是常用的, 所以保證小數(shù)值及負(fù)數(shù)的小數(shù)值盡可能的占少位
    '''
    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):
        # 每個(gè)字節(jié)的最高位來表示有沒有下一位, 若最高位為0, 則已完畢
        b = real & 0x7F
        real >>= 7
        if real > 0:
            buffer.write_u8(b | 0x80)
        else:
            buffer.write_u8(b)
            break
寫入字符串, 把字符串變成索引值, 如果協(xié)議里有大量重復(fù)的字符串可大大的節(jié)約協(xié)議的長(zhǎng)度
@staticmethod
def encode_str_idx(buffer: ByteBuffer, value):
    '''
    寫入字符串索引值, 在數(shù)值區(qū)里的所有字符串默認(rèn)會(huì)被寫成索引值
    如果重復(fù)的字符串則會(huì)返回相同的索引值(varint)
    '''
    idx = buffer.add_str(value)
    TPPacker.encode_type(buffer, TP_DATA_TYPE.TYPE_STR_IDX)
    TPPacker.encode_varint(buffer, idx)
寫入各種對(duì)應(yīng)的類型
@staticmethod        
def encode_field(buffer: ByteBuffer, value, pattern=None):
    '''
    先寫入類型的值(u8), 則根據(jù)類型寫入類型對(duì)應(yīng)的的數(shù)據(jù)
    '''
    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):
    '''
    寫入數(shù)組的長(zhǎng)度, 再寫入各各元素的值
    '''
    TPPacker.encode_varint(buffer, len(value))
    for v in value:
        TPPacker.encode_field(buffer, v)
        
@staticmethod
def encode_map(buffer: ByteBuffer, value):
    '''
    寫入map的長(zhǎng)度, 再分別寫入map各元素的key, value值
    '''
    TPPacker.encode_varint(buffer, len(value))
    for k in value:
        TPPacker.encode_field(buffer, k)
        TPPacker.encode_field(buffer, value[k])
寫入一條協(xié)議
@staticmethod
def encode_proto(buffer: ByteBuffer, name, infos):
    '''
    寫入?yún)f(xié)議名稱, 然后寫入字符串索引區(qū)(即字符串?dāng)?shù)組), 然后再寫入?yún)f(xié)議的詳細(xì)數(shù)據(jù)
    '''
    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源碼地址

相關(guān)連接

協(xié)議地址https://github.com/tickbh/TunmProto


網(wǎng)頁題目:tunm二進(jìn)制協(xié)議在python上的實(shí)現(xiàn)
網(wǎng)站鏈接:http://weahome.cn/article/dsogjoo.html

其他資訊

在線咨詢

微信咨詢

電話咨詢

028-86922220(工作日)

18980820575(7×24)

提交需求

返回頂部