这个主题专门针对Python的数据类型与C类型做一个对应。很多系统层面的C的开发基本上在Python中可以得到对应的移植应用。
比如:在网络通信中,解析IP,UDP等协议包的时候,会用到以位为单位的内存操作。
本主题的内容:
- 把C中以为单位的内存中数据转换为Python中对应数据;
- 读取网络的IP数据包,并使用Python解析数据包中每个对应数据。
准备
下面是IP协议数据包格式:
IP数据包格式说明
一. ctypes类型、C类型与Python类型
1. 类型对应表
ctypes type | C type | Python type |
---|---|---|
c_bool | _Bool | bool (1) |
c_char | char | 1-character bytes object |
c_wchar | wchar_t | 1-character string |
c_byte | char | int |
c_ubyte | unsigned char | int |
c_short | short | int |
c_ushort | unsigned short | int |
c_int | int | int |
c_uint | unsigned int | int |
c_long | long | int |
c_ulong | unsigned long | int |
c_longlong | __int64 or long long | int |
c_ulonglong | unsigned __int64 or unsigned long long | int |
c_size_t | size_t | int |
c_ssize_t | ssize_t or Py_ssize_t | int |
c_float | float | float |
c_double | double | float |
c_longdouble | long double | float |
c_char_p | char * (NUL terminated) | bytes object or None |
c_wchar_p | wchar_t * (NUL terminated) | string or None |
c_void_p | void * | int or None |
注意:
1. 上面类型还有别名,比如c_byte
的别名就是class ctypes.c_uint8
,别名体现了位数。
2. 上面所有类型都继承class ctypes._SimpleCData
,该类只有唯一的属性value
。
2. ctype类型使用例子
代码1:整数
import ctypes
v = ctypes.c_int ( 2 )
print ( v )
print ( v.value )
结果1:
c_int(2)
2
代码2:字符串
s = ctypes.c_wchar_p ("这是一个字符串" )
print ( s )
print ( s.value )
结果2:
c_wchar_p(4509017776)
这是一个字符串
ctypes类型是Python类,可以转换为Python内置类型,该类的主要作用就是就是可以控制Python数据的位数。可以构造2个字节的整数等,这样对数据进行更加精确的操作。
3. 从字节序列中拷贝
每个对应的类型还提供静态方法来产生对应的ctypes数据类型,这些静态方法来自所有ctypes类型的根类_CData类,该类不是public的,但其方法可以通过继承的方式在ctypes的每个类型中直接使用:
函数 | 函数说明 |
---|---|
from_buffer(source[, offset]) | 共享字节序列,产生对应的数据 |
from_buffer_copy(source[, offset]) | 从字节序列中拷贝,产生对应的数据 |
from_address(address) | 从一个地址产生对应的数据 |
from_param(obj) | 把一个python的数据obj,适配成C的参数 |
in_dll(library, name) | 加载在共享库中定义的变量 |
代码:(从动态库与地址加载例子暂时不列出来)
#从其他字节序列中拷贝
v2=ctypes.c_int.from_buffer(v,0)
print(v2,v2.value)
a=20
v3=ctypes.c_int.from_param(a)
print(v3)
结果:
c_int(2) 2
<cparam 'i' (20)>
提示:
1. 有了ctypes,python的数据可以采用更优的内存存储,比如在Python没有1字节或者2字节整数,使用ctypes就可以产生2字节整数(类型为c_short或者c_int16),这是Python类型没有办法实现的事情。
2. 更加有用的是,我们可以利用from_buffer函数直接使用2字节转换成整数(在后面的综合例子中可以说明)。
3. ctypes类型可以作为字节序列使用。
二、在Python中使用结构体
Python中没有结构体,通过结构体,可以批量存储不同字节的数据,尤其在把字节序列批量转换成多个数据的时候特别有用,比如把IP数据包按照格式读取特别方便。同时Structure的父类也是_CData,也可以调用from_buffer等函数。
1. 使用python内置数据构造结构体
构造Python结构体,遵循如下几个步骤:
|-继承ctypes.Structure类;
|-定义结构体每个字段的名字与类型
|-构造结构体对象
|-访问结构体中数据
其中字段的定义在fields中定义。fields是个list类型,每个元素由元组构成('字段名',类型,位数)
代码:
import ctypes
#结构体
class Point(ctypes.Structure):
_fields_=[ ("x", ctypes.c_int),
("y", ctypes.c_byte, 4)]
p=Point(200,5)
print(p)
print(p.x,p.y)
结果:
<__main__.Point object at 0x10d6a72f0>
200 5
2. 使用字节序列构造结构体
使用字节序列构造结构体,需要把字节序列拷贝到结构体,或者共享字节序列空间,可以使用两种方式:
|-方式一:覆盖__new__
,定制内存存储方式
|-方式二:使用from_buffer
函数直接返回。
实际上上述两种方式都要使用from_buffer
构造存储空间。
方式一实现代码:
import ctypes
#结构体
class Point(ctypes.Structure):
_fields_=[("x",ctypes.c_byte,4),("y",ctypes.c_byte,4)]
def __new__(self, buf):
return self.from_buffer(buf,0)
a=ctypes.c_byte(20)
p2=Point(a)
print(p2.x,p2.y)
#p3=Point(2,2) #这种方式就不能再使用,参数个数无法匹配
方式一结果:
4 1
方式二实现代码:
import ctypes
#结构体
class Point(ctypes.Structure):
_fields_=[("x",ctypes.c_byte,4),("y",ctypes.c_byte,4)]
a=ctypes.c_byte(20)
p2=Point.from_buffer(a,0)
print(p2)
print(p2.x,p2.y)
方式二结果:
<__main__.Point object at 0x107c171e0>
4 1
在高位第个位,等于,在低位,大家可以再回顾下二进制运算的游戏规则。
方式一还可以在init构造器中对字段数据进行处理:
import ctypes
#结构体
class Point(ctypes.Structure):
_fields_=[("x",ctypes.c_byte,4),("y",ctypes.c_byte,4)]
def __new__(self, buf):
return self.from_buffer(buf,0)
#参数与__new__保持一致
def __init__(self,buf):
self.x=6
a=ctypes.c_byte(20)
p2=Point(a)
print(p2.x,p2.y)
运行结果:
6 1
3. 使用结构体解析IP数据包
注意:下面代码在Mac OS与Linux系统下运行,需要root用户。
#!/usr/bin/python
#coding=utf-8
import socket
import struct
from ctypes import *
class IP(Structure):
_fields_ = [
("ihl", c_ubyte, 4),
("version", c_ubyte, 4),
("tos", c_ubyte),
("len", c_ushort),
("id", c_ushort),
("offset", c_ushort),
("ttl", c_ubyte),
("protocol_num", c_ubyte),
("sum", c_ushort),
("src", c_uint),
("dst", c_uint),
]
def __new__(self, socket_buffer=None):
return self.from_buffer_copy(socket_buffer)
def __init__(self, socket_buffer=None):
self.src_address = socket.inet_ntoa(struct.pack("<I", self.src))
self.dst_address = socket.inet_ntoa(struct.pack("<I", self.dst))
self.ver=self.version
sk=socket.socket(socket.AF_INET,socket.SOCK_RAW,0)
while True:
buf=sk.recv(2048,0)
ip_header = IP(buf[:20]) #可以使用from_buffer
print(ip_header.src_address)
print(ip_header.dst_address)
print(ip_header.ver)
说明:
(1)其中使用struct在Python内置类型与字节序列的转换是另外一个话题,在单独的篇幅说明。
(2)关于C动态库,函数指针,也使用单独的篇幅来说明。
资源
本主题代码列表:
|-ctypes01_type.py
|-ctypes02_struct.py
|-ctypes03_struct.py
|-ctypes04_struct_new.py
|-ctypes05_struct_init.py
下载地址:
https://github.com/QiangAI/PythonSkill/tree/master/AdvPython/04ctypes
网友评论