面對網絡協議,在組包拆包時,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)
運行結果:
格式長度: 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'\x10\x02\x00\x00\x00\x0c\x10\x03\x10\x04\x10\x05' 0x1002 12 ['0x1003', '0x1004', '0x1005']
參考資料
- https://docs.python.org/zh-cn/3/library/struct.html