最近在學習python網絡編程這一塊,在寫簡單的socket通訊代碼時,遇到了struct這個模塊的使用,當時不太清楚這到底有和做用,後來查閱了相關資料大概瞭解了,在這裏作一下簡單的總結。python
瞭解c語言的人,必定會知道struct結構體在c語言中的做用,它定義了一種結構,裏面包含不一樣類型的數據(int,char,bool等等),方便對某一結構對象進行處理。而在網絡通訊當中,大多傳遞的數據是以二進制流(binary data)存在的。當傳遞字符串時,沒必要擔憂太多的問題,而當傳遞諸如int、char之類的基本數據的時候,就須要有一種機制將某些特定的結構體類型打包成二進制流的字符串而後再網絡傳輸,而接收端也應該能夠經過某種機制進行解包還原出原始的結構體數據。python中的struct模塊就提供了這樣的機制,該模塊的主要做用就是對python基本類型值與用python字符串格式表示的C struct類型間的轉化(This module performs conversions between Python values and C structs represented as Python strings.)。stuct模塊提供了很簡單的幾個函數,下面寫幾個例子。編程
一、基本的pack和unpackapi
struct提供用format specifier方式對數據進行打包和解包(Packing and Unpacking)。例如:網絡
1
2
3
4
5
6
7
8
9
10
11
12
|
import
struct
import
binascii
values
=
(
1
,
'abc'
,
2.7
)
s
=
struct.Struct(
'I3sf'
)
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
'Packed Value :'
, binascii.hexlify(packed_data)
print
'Unpacked Type :'
,
type
(unpacked_data),
' Value:'
, unpacked_data
|
輸出:socket
Original values: (1, 'abc', 2.7)
Format string : I3sf
Uses : 12 bytes
Packed Value : 0100000061626300cdcc2c40
Unpacked Type : <type 'tuple'> Value: (1, 'abc', 2.700000047683716)函數
代碼中,首先定義了一個元組數據,包含int、string、float三種數據類型,而後定義了struct對象,並制定了format‘I3sf’,I 表示int,3s表示三個字符長度的字符串,f 表示 float。最後經過struct的pack和unpack進行打包和解包。經過輸出結果能夠發現,value被pack以後,轉化爲了一段二進制字節串,而unpack能夠把該字節串再轉換回一個元組,可是值得注意的是對於float的精度發生了改變,這是由一些好比操做系統等客觀因素所決定的。打包以後的數據所佔用的字節數與C語言中的struct十分類似。定義format能夠參照官方api提供的對照表:性能
二、字節順序學習
另外一方面,打包的後的字節順序默認上是由操做系統的決定的,固然struct模塊也提供了自定義字節順序的功能,能夠指定大端存儲、小端存儲等特定的字節順序,對於底層通訊的字節順序是十分重要的,不一樣的字節順序和存儲方式也會致使字節大小的不一樣。在format字符串前面加上特定的符號便可以表示不一樣的字節順序存儲方式,例如採用小端存儲 s = struct.Struct(‘<I3sf’)就能夠了。官方api library 也提供了相應的對照列表:spa
三、利用buffer,使用pack_into和unpack_from方法操作系統
使用二進制打包數據的場景大部分都是對性能要求比較高的使用環境。而在上面提到的pack方法都是對輸入數據進行操做後從新建立了一個內存空間用於返回,也就是說咱們每次pack都會在內存中分配出相應的內存資源,這有時是一種很大的性能浪費。struct模塊還提供了pack_into() 和 unpack_from()的方法用來解決這樣的問題,也就是對一個已經提早分配好的buffer進行字節的填充,而不會每次都產生一個新對象對字節進行存儲。
1
2
3
4
5
6
7
8
9
10
11
12
|
import
struct
import
binascii
import
ctypes
values
=
(
1
,
'abc'
,
2.7
)
s
=
struct.Struct(
'I3sf'
)
prebuffer
=
ctypes.create_string_buffer(s.size)
print
'Before :'
,binascii.hexlify(prebuffer)
s.pack_into(prebuffer,
0
,
*
values)
print
'After pack:'
,binascii.hexlify(prebuffer)
unpacked
=
s.unpack_from(prebuffer,
0
)
print
'After unpack:'
,unpacked
|
輸出:
Before : 000000000000000000000000
After pack: 0100000061626300cdcc2c40
After unpack: (1, 'abc', 2.700000047683716)
對比使用pack方法打包,pack_into 方法一直是在對prebuffer對象進行操做,沒有產生多餘的內存浪費。另外須要注意的一點是,pack_into和unpack_from方法均是對string buffer對象進行操做,並提供了offset參數,用戶能夠經過指定相應的offset,使相應的處理變得更加靈活。例如,咱們能夠把多個對象pack到一個buffer裏面,而後經過指定不一樣的offset進行unpack:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
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)