Python之struct介绍及详解(与C/C++通信结构体的交互)
用處
?
struct模塊中的函數(shù)
| pack(fmt,v1,v2…) | string | 按照給定的格式(fmt),把數(shù)據(jù)轉(zhuǎn)換成字符串(字節(jié)流),并將該字符串返回. |
| pack_into(fmt,buffer,offset,v1,v2…) | None | 按照給定的格式(fmt),將數(shù)據(jù)轉(zhuǎn)換成字符串(字節(jié)流),并將字節(jié)流寫入以offset開始的buffer中.(buffer為可寫的緩沖區(qū),可用array模塊) |
| unpack(fmt,v1,v2…..) | tuple | 按照給定的格式(fmt)解析字節(jié)流,并返回解析結(jié)果 |
| pack_from(fmt,buffer,offset) | tuple | 按照給定的格式(fmt)解析以offset開始的緩沖區(qū),并返回解析結(jié)果 |
| calcsize(fmt) | size of fmt | 計算給定的格式(fmt)占用多少字節(jié)的內(nèi)存,注意對齊方式 |
格式化字符串
當(dāng)打包或者解包的時,需要按照特定的方式來打包或者解包.該方式就是格式化字符串,它指定了數(shù)據(jù)類型,除此之外,還有用于控制字節(jié)順序、大小和對齊方式的特殊字符.
對齊方式
為了同c中的結(jié)構(gòu)體交換數(shù)據(jù),還要考慮c或c++編譯器使用了字節(jié)對齊,通常是以4個字節(jié)為單位的32位系統(tǒng),故而struct根據(jù)本地機器字節(jié)順序轉(zhuǎn)換.可以用格式中的第一個字符來改變對齊方式.定義如下
| @(默認(rèn)) | 本機 | 本機 | 本機,湊夠4字節(jié) |
| = | 本機 | 標(biāo)準(zhǔn) | none,按原字節(jié)數(shù) |
| < | 小端 | 標(biāo)準(zhǔn) | none,按原字節(jié)數(shù) |
| > | 大端 | 標(biāo)準(zhǔn) | none,按原字節(jié)數(shù) |
| ! | network(大端) | 標(biāo)準(zhǔn) | none,按原字節(jié)數(shù) |
如果不懂大小端,見大小端參考網(wǎng)址.
格式符
| x | pad byte(填充字節(jié)) | no value | ? |
| c | char | string of length 1 | 1 |
| b | signed char | integer | 1 |
| B | unsigned char | integer | 1 |
| ? | _Bool | bool | 1 |
| h | short | integer | 2 |
| H | unsigned short | integer | 2 |
| i | int | integer | 4 |
| I(大寫的i) | unsigned int | integer | 4 |
| l(小寫的L) | long | integer | 4 |
| L | unsigned long | long | 4 |
| q | long long | long | 8 |
| Q | unsigned long long | long | 8 |
| f | float | float | 4 |
| d | double | float | 8 |
| s | char[] | string | ? |
| p | char[] | string | ? |
| P | void * | long | ? |
注- -!
進制轉(zhuǎn)化:
# 獲取用戶輸入十進制數(shù) dec = int(input("輸入數(shù)字:"))print("十進制數(shù)為:", dec) print("轉(zhuǎn)換為二進制為:", bin(dec)) print("轉(zhuǎn)換為八進制為:", oct(dec)) print("轉(zhuǎn)換為十六進制為:", hex(dec))- 16進制轉(zhuǎn)10進制: int('0x10', 16) ?==> ?16
Python沒有專門處理字節(jié)的數(shù)據(jù)類型。但由于b'str'可以表示字節(jié),所以,字節(jié)數(shù)組=二進制str。而在C語言中,我們可以很方便地用struct、union來處理字節(jié),以及字節(jié)和int,float的轉(zhuǎn)換。
在Python中,比方說要把一個32位無符號整數(shù)變成字節(jié),也就是4個長度的bytes,你得配合位運算符這么寫:
>>> n = 10240099 >>> b1 = (n & 0xff000000) >> 24 >>> b2 = (n & 0xff0000) >> 16 >>> b3 = (n & 0xff00) >> 8 >>> b4 = n & 0xff >>> bs = bytes([b1, b2, b3, b4]) >>> bs b'\x00\x9c@c'非常麻煩。如果換成浮點數(shù)就無能為力了。
好在Python提供了一個struct模塊來解決bytes和其他二進制數(shù)據(jù)類型的轉(zhuǎn)換。
?
pack
struct的pack函數(shù)把任意數(shù)據(jù)類型變成bytes:
>>> import struct >>> struct.pack('>I', 10240099) b'\x00\x9c@c'pack的第一個參數(shù)是處理指令,'>I'的意思是:
>表示字節(jié)順序是big-endian,也就是網(wǎng)絡(luò)序,I表示4字節(jié)無符號整數(shù)。
后面的參數(shù)個數(shù)要和處理指令一致。
?
unpack
unpack把bytes變成相應(yīng)的數(shù)據(jù)類型:
>>> struct.unpack('>IH', b'\xf0\xf0\xf0\xf0\x80\x80') (4042322160, 32896)根據(jù)>IH的說明,后面的bytes依次變?yōu)?strong>I:4字節(jié)無符號整數(shù)和H:2字節(jié)無符號整數(shù)。
所以,盡管Python不適合編寫底層操作字節(jié)流的代碼,但在對性能要求不高的地方,利用struct就方便多了。
?
struct模塊定義的數(shù)據(jù)類型可以參考Python官方文檔:
https://docs.python.org/3/library/struct.html#format-characters
Windows的位圖文件(.bmp)是一種非常簡單的文件格式,我們來用struct分析一下。
首先找一個bmp文件,沒有的話用“畫圖”畫一個。
讀入前30個字節(jié)來分析:
>>> s = b'\x42\x4d\x38\x8c\x0a\x00\x00\x00\x00\x00\x36\x00\x00\x00\x28\x00\x00\x00\x80\x02\x00\x00\x68\x01\x00\x00\x01\x00\x18\x00'BMP格式采用小端方式存儲數(shù)據(jù),文件頭的結(jié)構(gòu)按順序如下:
兩個字節(jié):'BM'表示W(wǎng)indows位圖,'BA'表示OS/2位圖;一個4字節(jié)整數(shù):表示位圖大小;一個4字節(jié)整數(shù):保留位,始終為0;一個4字節(jié)整數(shù):實際圖像的偏移量;一個4字節(jié)整數(shù):Header的字節(jié)數(shù);一個4字節(jié)整數(shù):圖像寬度;一個4字節(jié)整數(shù):圖像高度;一個2字節(jié)整數(shù):始終為1;一個2字節(jié)整數(shù):顏色數(shù)。
所以,組合起來用unpack讀取:
>>> struct.unpack('<ccIIIIIIHH', s) (b'B', b'M', 691256, 0, 54, 40, 640, 360, 1, 24)結(jié)果顯示,b'B'、b'M'說明是Windows位圖,位圖大小為640x360,顏色數(shù)為24。
請編寫一個bmpinfo.py,可以檢查任意文件是否是位圖文件,如果是,打印出圖片大小和顏色數(shù)。
# -*- coding: utf-8 -*-import base64,structbmp_data = base64.b64decode('Qk1oAgAAAAAAADYAAAAoAAAAHAAAAAoAAAABABAAAAAAADICAAASCwAAEgsAAAAAAAAAAAAA/3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9/AHwAfAB8AHwAfAB8AHwAfP9//3//fwB8AHwAfAB8/3//f/9/AHwAfAB8AHz/f/9//3//f/9//38AfAB8AHwAfAB8AHwAfAB8AHz/f/9//38AfAB8/3//f/9//3//fwB8AHz/f/9//3//f/9//3//f/9/AHwAfP9//3//f/9/AHwAfP9//3//fwB8AHz/f/9//3//f/9/AHwAfP9//3//f/9//3//f/9//38AfAB8AHwAfAB8AHwAfP9//3//f/9/AHwAfP9//3//f/9//38AfAB8/3//f/9//3//f/9//3//fwB8AHwAfAB8AHwAfAB8/3//f/9//38AfAB8/3//f/9//3//fwB8AHz/f/9//3//f/9//3//f/9/AHwAfP9//3//f/9/AHwAfP9//3//fwB8AHz/f/9/AHz/f/9/AHwAfP9//38AfP9//3//f/9/AHwAfAB8AHwAfAB8AHwAfAB8/3//f/9/AHwAfP9//38AfAB8AHwAfAB8AHwAfAB8/3//f/9//38AfAB8AHwAfAB8AHwAfAB8/3//f/9/AHwAfAB8AHz/fwB8AHwAfAB8AHwAfAB8AHz/f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//3//f/9//38AAA==')def bmp_info(data): str = struct.unpack('<ccIIIIIIHH',data[:30]) #bytes類也有切片方法if str[0]==b'B' and str[1]==b'M':print("這是位圖文件")return {'width': str[-4],'height': str[-3],'color': str[-1]}else:print("這不是位圖文件")if __name__ == '__main__':bmp_info(bmp_data)print('ok')示例:
現(xiàn)在我們有了格式字符串,也知道了封裝函數(shù),那現(xiàn)在先通過一兩個例子看一看。
?例一:比如有一個報文頭部在C語言中是這樣定義的
struct header {unsigned short? usType;char[4]?????????acTag;unsigned int????uiVersion;unsigned int????uiLength; };在C語言對將該結(jié)構(gòu)體封裝到一塊緩存中是很簡單的,可以使用memcpy()實現(xiàn)。在Python中,使用struct就需要這樣:
str = struct.pack('B4sII', 0x04, 'aaaa', 0x01, 0x0e)
'B4sII'? ------?? 有一個unsigned short、char[4], 2個unsigned int。其中s之前的數(shù)字說明了字符串的大小?。
type, tag, version, length = struct.unpack('B4sll', str)
?
class struct.Struct(format)
返回一個struct對象(結(jié)構(gòu)體,參考C)。
該對象可以根據(jù)格式化字符串的格式來讀寫二進制數(shù)據(jù)。
第一個參數(shù)(格式化字符串)可以指定字節(jié)的順序。
默認(rèn)是根據(jù)系統(tǒng)來確定,也提供自定義的方式,只需要在前面加上特定字符即可:
struct.Struct('>I4sf')
常見方法和屬性:
方法
pack (v1, v2, …)
返回一個字節(jié)流對象。
按照fmt(格式化字符串)的格式來打包參數(shù)v1,v2,...。
通俗的說就是:
首先將不同類型的數(shù)據(jù)對象放在一個“組”中(比如元組(1,'good',1.22)),
然后打包(“組”轉(zhuǎn)換為字節(jié)流對象),最后再解包(將字節(jié)流對象轉(zhuǎn)換為“組”)。
pack_into(buffer, offset, v1, v2, …)
根據(jù)格式字符串fmt包裝值v1,v2,...,并將打包的字節(jié)寫入從位置偏移開始的可寫緩沖buffer。?請注意,offset是必需的參數(shù)。
unpack_from(buffer, offset=0)
根據(jù)格式字符串fmt,從位置偏移開始從緩沖區(qū)解包。 結(jié)果是一個元組,即使它只包含一個項目。?緩沖區(qū)的大小(以字節(jié)為單位,減去偏移量)必須至少為格式所需的大小,如calcsize()所反映的。
屬性
format
格式化字符串。
size
結(jié)構(gòu)體的大小。
實例:
1. 通常的打包和解包
# -*- coding: utf-8 -*- """ 打包和解包 """ import?struct import binasciivalues = (1, b'good', 1.22) #查看格式化對照表可知,字符串必須為字節(jié)流類型。 s =?struct.Struct('I4sf') 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)?結(jié)果:
Original values: (1, b'good', 1.22)
Format string : b'I4sf'
Uses : 12 bytes
Packed Value : b'01000000676f6f64f6289c3f'
Unpacked Type : <class 'tuple'> ?Value: (1, b'good', 1.2200000286102295)
說明:
首先將數(shù)據(jù)對象放在了一個元組中,然后創(chuàng)建一個Struct對象,并使用pack()方法打包該元組;最后解包返回該元組。
這里使用到了binascii.hexlify(data)函數(shù)。
binascii.hexlify(data)
返回字節(jié)流的十六進制字節(jié)流。
>>> a = 'hello' >>> b = a.encode() >>> b b'hello' >>> c = binascii.hexlify(b) >>> c b'68656c6c6f'?
2. 使用buffer來進行打包和解包
使用通常的方式來打包和解包會造成內(nèi)存的浪費,所以python提供了buffer的方式:
# -*- coding: utf-8 -*- """ 通過buffer方式打包和解包 """ import?struct import binascii import ctypesvalues = (1, b'good', 1.22) #查看格式化字符串可知,字符串必須為字節(jié)流類型。 s =?struct.Struct('I4sf') buff = ctypes.create_string_buffer(s.size) packed_data = s.pack_into(buff,0,*values) unpacked_data = s.unpack_from(buff,0)print('Original values:', values) print('Format string :', s.format) print('buff :', buff) print('Packed Value :', binascii.hexlify(buff)) print('Unpacked Type :', type(unpacked_data),?' Value:', unpacked_data)?結(jié)果:
Original values1: (1, b'good', 1.22)
Original values2: (b'hello', True)
buff : <ctypes.c_char_Array_18 object at 0x000000D5A5617348>
Packed Value : b'01000000676f6f64f6289c3f68656c6c6f01'
Unpacked Type : <class 'tuple'>??Value: (1, b'good', 1.2200000286102295)
說明:
針對buff對象進行打包和解包,避免了內(nèi)存的浪費。
這里使用到了函數(shù)
ctypes.create_string_buffer(init_or_size,size = None)
創(chuàng)建可變字符緩沖區(qū)。
返回的對象是c_char的ctypes數(shù)組。
init_or_size必須是一個整數(shù),它指定數(shù)組的大小,或者用于初始化數(shù)組項的字節(jié)對象。
?
3. 使用buffer方式來打包多個對象
# -*- coding: utf-8 -*- """ buffer方式打包和解包多個對象 """ import?struct import binascii import ctypesvalues1 = (1, b'good', 1.22) #查看格式化字符串可知,字符串必須為字節(jié)流類型。 values2 = (b'hello',True) s1 =?struct.Struct('I4sf') s2 =?struct.Struct('5s?') buff = ctypes.create_string_buffer(s1.size+s2.size) packed_data_s1 = s1.pack_into(buff,0,*values1) packed_data_s2 = s2.pack_into(buff,s1.size,*values2) unpacked_data_s1 = s1.unpack_from(buff,0) unpacked_data_s2 = s2.unpack_from(buff,s1.size)print('Original values1:', values1) print('Original values2:', values2) print('buff :', buff) print('Packed Value :', binascii.hexlify(buff)) print('Unpacked Type :', type(unpacked_data_s1),?' Value:', unpacked_data_s1) print('Unpacked Type :', type(unpacked_data_s2),?' Value:', unpacked_data_s2)?結(jié)果:
Original values2: (b'hello', True)
buff : <ctypes.c_char_Array_18 object at 0x000000D5A5617348>
Packed Value : b'01000000676f6f64f6289c3f68656c6c6f01'
Unpacked Type : <class 'tuple'> ?Value: (1, b'good', 1.2200000286102295)
Unpacked Type : <class 'tuple'> ?Value: (b'hello', True)
總結(jié)
以上是生活随笔為你收集整理的Python之struct介绍及详解(与C/C++通信结构体的交互)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: C/C++ getopt()函数的介绍及
- 下一篇: web靶机:kali linux 2.0