最近在學習python網(wǎng)絡(luò)編程這一塊,在寫簡單的socket通信代碼時享完,遇到了struct這個模塊的使用灼芭,當時不太清楚這到底有和作用,后來查閱了相關(guān)資料大概了解了般又,在這里做一下簡單的總結(jié)姿鸿。
? ? 了解c語言的人,一定會知道struct結(jié)構(gòu)體在c語言中的作用倒源,它定義了一種結(jié)構(gòu)苛预,里面包含不同類型的數(shù)據(jù)(int,char,bool等等),方便對某一結(jié)構(gòu)對象進行處理笋熬。而在網(wǎng)絡(luò)通信當中热某,大多傳遞的數(shù)據(jù)是以二進制流(binary data)存在的。當傳遞字符串時胳螟,不必擔心太多的問題昔馋,而當傳遞諸如int、char之類的基本數(shù)據(jù)的時候糖耸,就需要有一種機制將某些特定的結(jié)構(gòu)體類型打包成二進制流的字符串然后再網(wǎng)絡(luò)傳輸秘遏,而接收端也應(yīng)該可以通過某種機制進行解包還原出原始的結(jié)構(gòu)體數(shù)據(jù)。python中的struct模塊就提供了這樣的機制嘉竟,該模塊的主要作用就是對python基本類型值與用python字符串格式表示的C struct類型間的轉(zhuǎn)化(This module performs conversions between Python values and C structs represented as Python strings.)邦危。stuct模塊提供了很簡單的幾個函數(shù)洋侨,下面寫幾個例子。
1倦蚪、基本的pack和unpack
? ? struct提供用format specifier方式對數(shù)據(jù)進行打包和解包(Packing and Unpacking)希坚。例如:
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
復(fù)制
輸出:
Original values: (1, 'abc', 2.7) Format string : I3sf Uses : 12 bytes Packed Value : 0100000061626300cdcc2c40 Unpacked Type :? Value: (1, 'abc', 2.700000047683716)
代碼中,首先定義了一個元組數(shù)據(jù)陵且,包含int裁僧、string、float三種數(shù)據(jù)類型慕购,然后定義了struct對象聊疲,并制定了format‘I3sf’,I 表示int沪悲,3s表示三個字符長度的字符串获洲,f 表示 float。最后通過struct的pack和unpack進行打包和解包可训。通過輸出結(jié)果可以發(fā)現(xiàn),value被pack之后捶枢,轉(zhuǎn)化為了一段二進制字節(jié)串握截,而unpack可以把該字節(jié)串再轉(zhuǎn)換回一個元組,但是值得注意的是對于float的精度發(fā)生了改變烂叔,這是由一些比如操作系統(tǒng)等客觀因素所決定的谨胞。打包之后的數(shù)據(jù)所占用的字節(jié)數(shù)與C語言中的struct十分相似。定義format可以參照官方api提供的對照表:
2蒜鸡、字節(jié)順序
另一方面胯努,打包的后的字節(jié)順序默認上是由操作系統(tǒng)的決定的,當然struct模塊也提供了自定義字節(jié)順序的功能逢防,可以指定大端存儲叶沛、小端存儲等特定的字節(jié)順序,對于底層通信的字節(jié)順序是十分重要的忘朝,不同的字節(jié)順序和存儲方式也會導(dǎo)致字節(jié)大小的不同灰署。在format字符串前面加上特定的符號即可以表示不同的字節(jié)順序存儲方式,例如采用小端存儲 s = struct.Struct(‘<I3sf’)就可以了局嘁。官方api library 也提供了相應(yīng)的對照列表:
3溉箕、利用buffer,使用pack_into和unpack_from方法
? 使用二進制打包數(shù)據(jù)的場景大部分都是對性能要求比較高的使用環(huán)境悦昵。而在上面提到的pack方法都是對輸入數(shù)據(jù)進行操作后重新創(chuàng)建了一個內(nèi)存空間用于返回肴茄,也就是說我們每次pack都會在內(nèi)存中分配出相應(yīng)的內(nèi)存資源,這有時是一種很大的性能浪費但指。struct模塊還提供了pack_into() 和 unpack_from()的方法用來解決這樣的問題寡痰,也就是對一個已經(jīng)提前分配好的buffer進行字節(jié)的填充抗楔,而不會每次都產(chǎn)生一個新對象對字節(jié)進行存儲。
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
復(fù)制
輸出:
Before : 000000000000000000000000 After pack: 0100000061626300cdcc2c40 After unpack: (1, 'abc', 2.700000047683716) 對比使用pack方法打包氓癌,pack_into 方法一直是在對prebuffer對象進行操作谓谦,沒有產(chǎn)生多余的內(nèi)存浪費。另外需要注意的一點是贪婉,pack_into和unpack_from方法均是對string buffer對象進行操作反粥,并提供了offset參數(shù),用戶可以通過指定相應(yīng)的offset疲迂,使相應(yīng)的處理變得更加靈活才顿。例如,我們可以把多個對象pack到一個buffer里面尤蒿,然后通過指定不同的offset進行unpack:
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)
復(fù)制
輸出:
Before : 0000000000000000000000000000000000000000 After pack: 0100000061626300cdcc2c406465666765000000 (1, 'abc', 2.700000047683716) ('defg', 101)