Luckylau's Blog

Python中的struct模块

​ python的struct模块,是在查看RYU控制器Openflow协议的实现源码接触到的。RYU控制器解包和封包就是用struct模块实现的。

​ 在C语言中,struct结构体里面可以包含不同数据类型,比如int ,char,bool等。但是一旦涉及到网络通信时,传递的是二进制数据流(binary data)。对于二进制字符串,不必担心,但是对于如int,char等基本数据类型,需要有一种机制将这些特定的结构体类型打包成二进制流的字符串然后再在网络传输,同时接收端也需要通过某种机制进行解包还原出原始的结构体数据。

​ python中的struct模块就提供了这样的机制,该模块的主要作用就是对python基本类型值与用python字符串格式表示的C struct类型间的转化,如下图:

1.简单演示

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import struct
import binascii
values=(2017,'luckylau0',1.19)
s=struct.Struct('I9sf')
packed_data = s.pack(*values)#打包
unpacked_data = s.unpack(packed_data)#解包
print 'Original values:', values
print 'Format string :', s.format
print 'Uses :', s.size, 'bytes'
print struct.calcsize('I9sf')
print 'Packed Value :', binascii.hexlify(packed_data)
print 'Unpacked Type :', type(unpacked_data), ' Value:', unpacked_data
#输出
Original values: (2017, 'luckylau0', 1.19)
Format string : I9sf
Uses : 20 bytes
20
Packed Value : e10700006c75636b796c617530000000ec51983f
Unpacked Type : <type 'tuple'> Value: (2017, 'luckylau0', 1.190000057220459)

​ 代码中,首先定义了一个元组数据,包含int、string、float三种数据类型,然后定义了struct对象,并制定了format‘I8sf’,I 表示int ,8s表示八个字符长度的字符串,f 表示 float。最后通过struct的pack和unpack进行打包和解包。通过输出结果可以发现,value被pack之后,转化为了一段二进制字节串,而unpack可以把该字节串再转换回一个元组,但是值得注意的是对于float的精度发生了改变,这是由一些比如操作系统等客观因素所决定的。

2.字节顺序

​ 打包的后的字节顺序默认上是由操作系统的决定的,当然struct模块也提供了自定义字节顺序的功能

​ 例如采用小端存储 s = struct.Struct(‘<I3sf’)

3.利用buffer,使用pack_into和unpack_from方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import struct
import binascii
import ctypes
values1 = (1, 'abc', 2.7)
values2 = ('defg',101)
s1 = struct.Struct('I3sf')
s2 = struct.Struct('4sI')
prebuffer = ctypes.create_string_buffer(s1.size+s2.size)
print 'Before :',binascii.hexlify(prebuffer)
s1.pack_into(prebuffer,0,*values1)
s2.pack_into(prebuffer,s1.size,*values2)
print 'After pack:',binascii.hexlify(prebuffer)
print s1.unpack_from(prebuffer,0)
print s2.unpack_from(prebuffer,s1.size)
#输出
Before : 0000000000000000000000000000000000000000
After pack: 0100000061626300cdcc2c406465666765000000
(1, 'abc', 2.700000047683716)
('defg', 101)

​ 使用二进制打包数据的场景大部分都是对性能要求比较高的使用环境,所以上面提到的pack方法都是对输入数据进行操作后重新创建了一个内存空间用于返回,也就是说我们每次pack都会在内存中分配出相应的内存资源,这有时是一种很大的性能浪费。pack_into() 和 unpack_from()的方法就是对一个已经提前分配好的buffer进行字节的填充,而不会每次都产生一个新对象对字节进行存储。在RYU控制器中就是使用这两种方法。

4.总结:

struct 模块

Python的struct库是一个简单的,高效的数据封装\解封装的库。主要包含5个函数:

struct.pack(fmt, v1, v2, …): 将V1,V2等值按照对应的fmt(format)进行封装。

struct.unpack(fmt, string): 将string按照fmt的格式解封。

struct.pack_into(fmt, buffer, offset, v1, v2, …): 将V1,V2等值按照对应的fmt(format)封装到buffer中,从初始位置offset开始。

struct.unpack_from(fmt, buffer[offset=0,]): 按照fmt的格式,从offset开始将buffer解封。 struct.calcsize(fmt): 计算对应的fmt的长度。

Luckylau wechat
如果对您有价值,看官可以打赏的!