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_unpack 和 unpack_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'
|
Author
Alfons
LastMod
0001-01-01
License
Creative Commons BY-NC-ND 3.0