struct库的使用

 

在解析自定义的二进制的文件格式,或者已知的一些文件格式的时候,比如png图片,tcp包,就需要struct库;

解析二进制文件长需要的函数有从buffer中读取char(一个字节),short(两个字节),int(4字节),string(n个char),这些struct都可以很好的胜任。官方说明

struct生成或者解析二进制的使用方法,很像格式化输出,即使用特定的字符来输出自己想要的格式;

函数说明

exceptstruct.error

struct.pack(fmt,v1,v2,...): 返回以fmt格式指定的v1,v2,…组成的string; 要求v1,v2,...能够严格匹配fmt

struct.pack_into(fmt,buffer,offset,v1,v2,...): 从buffer的offset处,组成以fmt格式指定组成的v1,v2,...

struct.unpack(fmt,string): 使用fmt解析string,返回结果为tuple,即使只包含一个值。要求len(string)calcsize(fmt)相同

struct.unpack_from(fmt,buffer[,offset=0]): 从buffer的offset处开始以fmt指定的格式解析,返回结果为tuple,要求len(buffer[offset:])>=calcsize(fmt)

struct.calcsize(fmt): 计算给定格式的字节大小

fmt可以指定字节序,字符类型等

大小端

做过相关网络数据包开发的都接触过本地序和网络序;

对于网络序,使用的大端序(big endian)。

本地序跟CPU相关

  1. 对于Intel X86和AMD64(X86-64)都是小端序(little-endian)

  2. Motorola 68000 和 PowerPC G5是大端序

  3. 对于ARM和Intel Itanium 是双端序

可以使用sys.byteorder来获取当前系统的类型。

那么什么是大端序,小端序?

大端序:数据的高位字节存放在地址的低端,低位字节存放在地址的高端

小端序:数据的高位自己存放在地址的高端,低位字节存放在地址的低端

数据的高位和低位:举例,int a = 0x12345678, 坐标12是高位字节,右边78是低位字节,从左到右,从高到低;

地址的高端和低端(假设是64位机器,一个地址是64位):

0x00000001

0x00000002

0x00000003

0x00000004

从上到下,由低到高,地址值小的为低端,地址值大的为高端。

假设从地址0x00000001开始存储数0x12345678,则

大端序存放方式(按原来顺序存放):

0x0000000112

0x0000000234

0x0000000356

0x0000000478

小端序存放方式(按颠倒顺序存放):

0x0000000178

0x0000000256

0x0000000334

0x0000000412

struct可以指定使用大端序还是小端序来解析或者生成数据。

Character Byte order Size Alignment
@ native native native
= native standard none
< little-endian standard none
> big-endian standard none
! network(=big-endian) standard none

如果不指定,@是默认值。

Native size和alignment由C语言的sizeof表达式决定, 即结构体的字节对齐规则。C语言的字节对齐问题

数据格式

Format C Type Python type Standard size Notes
x pad byte no value
c char string of length 1 1
b signed char integer 1 (3)
B unsigned char integer 1 (3)
? _Bool bool 1 (1)
h short integer 2 (3)
H unsigned short integer 2 (3)
i int integer 4 (3)
I unsigned int integer 4 (3)
l long integer 4 (3)
L unsigned long integer 4 (3)
q long long integer 8 (2),(3)
Q unsigned long long integer 8 (2),(3)
f float float 4 (4)
d double float 8 (4)
s char[] string
p char[] string
P void * integer (5),(3)

Notes:

  1. ? 是C99中的_Bool, 如果不可用, 则生成char, 一个字节;

  2. qQ只用当C编译器支持long long;

  3. 当pack一个非integer类型的时候,如果此非integer有__index__()函数,则pack__init__()的返回值;若没有__index__()或者调用__index__()报异常,则尝试__int__()函数,若没有__int__()则报DeprecationWarning;

  4. 对于浮点类型,f使用IEEE 754 binary32,d使用binary64标准

  5. P只在native byte ordering使用

  6. 4h等价于hhhh

  7. '10s'代表 10-byte string, '10c' 代表 10 characters

  8. pPascal string, 第一个字节代表长度,随后是字符串

举例

示例在big-endian机器,以native byte order,size, alignment

1
2
3
4
5
6
7
8
> >>> from struct import *
> >>> pack('hhl', 1, 2, 3)
> '\x00\x01\x00\x02\x00\x00\x00\x03'
> >>> unpack('hhl', '\x00\x01\x00\x02\x00\x00\x00\x03')
> (1, 2, 3)
> >>> calcsize('hhl')
> 8
>

示例代码

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
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import struct	
class BytesUtil(object):
@staticmethod
def bytes_to_int(byte_s):
# little_endian
result = 0
for b in reversed(byte_s):
result = result * 256 + int(b)
return result

@staticmethod
def get_byte(buffer, offset):
value = struct.unpack_from("B", buffer, offset)[0]
offset += 1
return value, offset

@staticmethod
def get_short(buffer, offset):
value = struct.unpack_from("H", buffer, offset)[0]
offset += 2
return value, offset

@staticmethod
def get_int(buffer, offset):
value = struct.unpack_from("I", buffer, offset)[0]
offset += 4
return value, offset

@staticmethod
def get_int24(buffer, offset):
value, offset = BytesUtil.get_byte_n(3, buffer, offset)
return BytesUtil.bytes_to_int(value), offset

@staticmethod
def get_byte_n(n, buffer, offset):
value = struct.unpack_from("%dB" % (n), buffer, offset)
offset += n
return value, offset

@staticmethod
def get_short_n(n, buffer, offset):
value = struct.unpack_from("%dH" % (n), buffer, offset)
offset += 2*n
return value, offset

@staticmethod
def get_int_n(n, buffer, offset):
value = struct.unpack_from("%dI" % (n), buffer, offset)
offset += 4*n
return value, offset