Python之struct模块

面对网络协议,在组包拆包时,python提供了struct模块,它可以帮助我们在python值和C语言的结构体之间相互转换,下面一起来了解struct的具体用法。

假设,我们的网络协议为消息id(unsigned short类型)及消息payload(unsigned int类型)组成,那么该如何进行组包拆包呢?如下例所示:

import struct

# 组包, 其中msg_id为0x1002、msg_payload为0x10070008
packet = struct.pack('>HI', 0x1002, 0x10070008)
print("packet: %s" % packet)
# 拆包
msg_id, msg_payload = struct.unpack_from('>HI', packet)
print("msg_id: %s , msg_payload: %s" % (hex(msg_id), hex(msg_payload)))

运行结果:

packet: b'\x10\x02\x10\x07\x00\x08'
msg_id: 0x1002 , msg_payload: 0x10070008

上述例子中,我们用到了pack(format, v1, v2, ...)函数组包及unpack(format, buffer)函数拆包,它们指定的消息格式为'>HI',即以大端字节序排列的unsigned short+unsigned int数据。

关于字节顺序的符号,官方定义如下:

关于格式类型的符号,官方定义如下:

为了方便我们计算format的长度,比如上例中'>HI'的长度,struct提供了calcsize(format)函数供我们调用,如下例所示:

import struct

fmt_len = struct.calcsize('>HI')
print("格式长度: %s" % fmt_len)

运行结果:

下面,我们进一步来了解pack_into(format, buffer, offset, v1, v2, ...)函数和unpack_from(format, buffer, offset=0)函数,它们在组包拆包时,可以指定所需的偏移量,这让组包拆包变得更加灵活。本文第一个例子中,网络协议为固定长度,但是更多时候,网络协议是可变长度的。假设,网络协议由消息id(unsigned short类型)、消息size(unsigned int类型)及可变长度的消息payload(若干个unsigned int类型)组成,那么该如何操作呢?下例将为大家解答。

import struct
import ctypes

def load_packet(msg_id, msg_size, msg_payload):
    packet = ctypes.create_string_buffer(msg_size)
    struct.pack_into('>HI', packet, 0, msg_id, msg_size)
    struct.pack_into('>%dH' % (int(msg_size-6)/2), packet, 6, *msg_payload)
    return packet

def unload_packet(packet):
    msg_id, msg_size = struct.unpack_from('>HI', packet, 0)
    msg_payload = struct.unpack_from('>%dH' % (int(msg_size-6)/2), packet, 6)
    return msg_id, msg_size, msg_payload

if __name__ == '__main__':
    packet = load_packet(0x1002, 12, (0x1003, 0x1004, 0x1005))
    print("packet: %s" % packet.raw)
    msg_id, msg_size, msg_payload = unload_packet(packet)
    print(hex(msg_id), msg_size, [hex(item) for item in msg_payload])

运行结果:

packet: b'\x10\x02\x00\x00\x00\x0c\x10\x03\x10\x04\x10\x05'
0x1002 12 ['0x1003', '0x1004', '0x1005']