struct 模块用于二进制数据,常用于处理网络数据。

struct主要函数包括:

  • pack(fmt, *args) - 根据条件将不同的变量打包在一起。fmt或待打包数据错误,返回异常。
  • unpack(fmt, string) - 根据条件将字符串解包成对应的变量。fmt或待解包字符串错误,返回异常。
  • calcsize(fmt) - 根据fmt返回此fmt处理数据的大小。
  • pack_into(fmt, buffer, offset, *args) - 按照fmt的格式将打包的数据写入可写buffer中。
  • unpack_from(fmt, buffer, offset=0) - 从buffer按照fmt格式读出数据。
  • iter_unpack(fmt, buffer) - 同unpack_from,返回的为迭代器。

一些标识符

符号字符对齐方式字符大小规则
@(默认)本机本机本机,字节对齐
=本机标准按原字节数
<小端标准按原字节数
>大端标准按原字节数
!network(大端)标准按原字节数

在不同的CPU架构下数据的存储方式会有所不同,有些CPU使用小端存储数据,有些则是使用大端。上面这些符号都是在 struct表达式 的开头指明。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
def Diff():
    a = 0x12345678

    print('{msg} default -> {result}'.format(msg = hex(a), result = ' '.join([hex(r) for r in struct.pack('i', a)])))
    print('{msg} little endian -> {result}'.format(msg = hex(a), result = ' '.join([hex(r) for r in struct.pack('<i', a)])))
    print('{msg} big endian -> {result}'.format(msg = hex(a), result = ' '.join([hex(r) for r in struct.pack('>i', a)])))

# output

0x12345678 default -> 0x78 0x56 0x34 0x12
0x12345678 little endian -> 0x78 0x56 0x34 0x12
0x12345678 big endian -> 0x12 0x34 0x56 0x78

上面的代码,展示了大小端读取数据的不同,可以看见,本机使用的是小端存储。不过按照人类的阅读习惯,还是大端存储比较易读,但对于机器来说,小端存储易于计算。

字节对齐的方式不同,打包的结果大小也会有所不同。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def Calcsize():
    print('ci -> {} bytes.'.format(struct.calcsize('ci')))  # 默认本机,字节补齐
    print('@ci -> {} bytes.'.format(struct.calcsize('@ci')))  # 本机size大小,字节补齐
    print('=ci -> {} bytes.'.format(struct.calcsize('=ci')))  # byte 顺序为本机,size 为标准字节数
    print('<ci -> {} bytes.'.format(struct.calcsize('<ci')))  # byte 顺序小端,size 为标准字节数
    print('>ci -> {} bytes.'.format(struct.calcsize('>ci')))  # byte 顺序大端,size 为标准字节数
    print('!ci -> {} bytes.'.format(struct.calcsize('!ci')))  # byte 顺序网络序(大端),size 为标准字节数

Calcsize()

# output
ci -> 8 bytes.
@ci -> 8 bytes.
=ci -> 5 bytes.
<ci -> 5 bytes.
>ci -> 5 bytes.
!ci -> 5 bytes.

默认情况下,本机的数据有字节对齐的操作,int类型为4字节,char为1字节,但字节对齐后,需要补齐剩下的3字节。

pack 和 unpack

pack 用于数据的打包,根据struct表达式,打包成对应的二进制数据,如果待打包的数据错误,会出现异常。

unpack 用于数据的解包,根据struct表达式,解包成对应的数据,返回值为tuple类型。如果待解包的数据或struct表达式错误,会出现异常。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
def Unpack(fmt, string):
    result = struct.unpack(fmt, string)
    print("unpack {string} use {fmt} -> {chars}".format(string = string, fmt = fmt, chars = tuple([hex(r) for r in result])))


def Pack(fmt, msg = (0x11223344, 0x55667788)):
    result = struct.pack(fmt, *msg)
    print("pack {msg} use \"{fmt}\" -> {chars}".format(fmt = fmt, chars = " ".join([hex(r) for r in result]), msg = result))
    return result

Unpack('ii', Pack('ii'))
print()
Unpack('<ii', Pack('<ii'))
print()
Unpack('>ii', Pack('>ii'))

# output
pack b'D3"\x11\x88wfU' use "ii" -> 0x44 0x33 0x22 0x11 0x88 0x77 0x66 0x55
unpack b'D3"\x11\x88wfU' use ii -> ('0x11223344', '0x55667788')

pack b'D3"\x11\x88wfU' use "<ii" -> 0x44 0x33 0x22 0x11 0x88 0x77 0x66 0x55
unpack b'D3"\x11\x88wfU' use <ii -> ('0x11223344', '0x55667788')

pack b'\x11"3DUfw\x88' use ">ii" -> 0x11 0x22 0x33 0x44 0x55 0x66 0x77 0x88
unpack b'\x11"3DUfw\x88' use >ii -> ('0x11223344', '0x55667788')

pack_into 和 unpack_from、iter_unpack

pack_into 为将数据打包并存放在可读可写的一段内存空间中。

unpack_from 为还原内存中的数据。

iter_unpackunpack_from 作用一样,不过返回的是一个可迭代对象。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
def PackInto(s):
    import ctypes

    fmt = "I{len}s".format(len = len(s))
    f = ctypes.create_string_buffer(struct.calcsize(fmt))

    print("Before pack buffer is -> {f}".format(f = f.raw))
    struct.pack_into(fmt, f, 0, *(len(s), s))
    print("After pack buffer is -> {f}".format(f = f.raw))

    return fmt, f


def UnpackFrom(fmt, buffer):
    result = struct.unpack_from(fmt, buffer, 0)
    print("Unpack from \'{f}\' -> {r} ({type})".format(f = fmt, r = result, type = type(result)))


def IterUnpack(fmt, buffer):
    result = struct.iter_unpack(fmt, buffer)
    print("Unpack from \'{f}\' -> {r} ({type})".format(f = fmt, r = result, type = type(result)))
    print("Unpack result -> ", [r for r in result])

UnpackFrom(*PackInto(b'hello world'))
print()
IterUnpack(*PackInto(b'hello world'))

#output
Before pack buffer is -> b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
After pack buffer is -> b'\x0b\x00\x00\x00hello world'
Unpack from 'I11s' -> (11, b'hello world') (<class 'tuple'>)

Before pack buffer is -> b'\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'
After pack buffer is -> b'\x0b\x00\x00\x00hello world'
Unpack from 'I11s' -> <unpack_iterator object at 0x7f084f61e9d8> (<class 'unpack_iterator'>)
Unpack result ->  [(11, b'hello world')]

Struct类

re 模块类似,struct 模块中的所有函数都需要创建 Struct实例 来执行对应的功能。

所以,实际上,使用时,先创建 Struct实例,然后使用实例的方法速度上会更快一些。

一些应用

判断大小端

在一些程序中,由于需要跨平台,可能需要进行大小端的判断,否则在一些数值计算时会出现奇怪的问题。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
def IsBigEndian():
    a = 0x12345678

    result = struct.pack('i', a)
    if hex(result[0]) == '0x78':
        print("Machine is little endian")
    else:
        print("Machine is big endian")  # human read

    import sys
    print("sys.byteorder -> ", sys.byteorder)  # use system byteorder

# output
Machine is little endian
sys.byteorder ->  little

网络流量

在使用网络传输数据时,不能够直接传输某些类型,例如int类型。需要使用struct模块进行转换。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
def PackNetworkPcap(fmt, s = b'hello world'):
    fmt = fmt.format(len = len(s))      # 根据字符串长度格式化表达式
    p_1 = struct.pack(fmt, len(s), s)
    print("pack {msg} use \"{fmt}\"  -> {chars}".format(msg = s, fmt = fmt, chars = p_1))
    return p_1

PackNetworkPcap('>H{len}s')
PackNetworkPcap('>I{len}s')
PackNetworkPcap('>L{len}s')
PackNetworkPcap('>Q{len}s')
PackNetworkPcap('>f{len}s')

# output
pack b'hello world' use ">H11s"  -> b'\x00\x0bhello world'
pack b'hello world' use ">I11s"  -> b'\x00\x00\x00\x0bhello world'
pack b'hello world' use ">L11s"  -> b'\x00\x00\x00\x0bhello world'
pack b'hello world' use ">Q11s"  -> b'\x00\x00\x00\x00\x00\x00\x00\x0bhello world'
pack b'hello world' use ">f11s"  -> b'A0\x00\x00hello world'