面对网络协议,在组包拆包时,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'x10x02x10x07x00x08' 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)
运行结果:
格式长度: 6
下面,我们进一步来了解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'x10x02x00x00x00x0cx10x03x10x04x10x05' 0x1002 12 ['0x1003', '0x1004', '0x1005']
参考资料
- https://docs.python.org/zh-cn/3/library/struct.html