[Python]ctypes+struct实现类c的结构化数据串行处理
1. 用C/C++实现的结构化数据处理
在涉及到比较底层的通信协议开发过程中, 往往需要开发语言能够有效的表达和处理所定义的通信协议的数据结构. 在这方面是C/C++语言是具有天然优势的: 通过struct, union, 和bit-fields, C/C++能够以一种最有效率也最自然的方式处理此类问题.
举例说明一下, 下图是智能电网用于远程自动抄表的通信协议的一部分
用C可以描述如下:
struct{unsigned char uRouter:1; //路由标识unsigned char uSubNode:1;//附属节点标识unsigned char uCM:1; //通信模块标识unsigned char uCD:1; //冲突检测unsigned char uLevel:4; //中继级别unsigned char uChannel:4;//信道标识unsigned char uErrBate:4;//纠错编码标识unsigned char uResBytes; //预计应答字节数unsigned short uSpeed:15; //通信波特率,BIN格式unsigned short uUnit:1; //0:bps;1:kbpsunsigned char uReserve;} Req;
这样不仅清楚的描述了完全符合通信协议要求的报文数据结构, 而且还有至少以下两个优点:
1. 对结构中的任意变量取址赋值取值极其方便, 如
struct Req r;r.uCD = 0;r.uChannel = 0x0F;
并不必费心的计算偏移量. 而且如果以后通信协议升级了, 只需要将数据结构定义更改即可, 其余代码完全不用变动.
2. 更重要的是, 这个数据结构在计算机内存中天然的就是按照通信协议的串行结构排列的(假设大端小端问题已设置正确), 只需要
struct Req r;...send((unsigned char *)&r, sizeof(r));
就可以以通信协议完全一致的格式将数据转换成字节流发送出去了. 而接收解析也同样方便:
struct Req rs;unsigned char rcv_buffer[100];...rcv(rcv_buffer sizeof(Req));memcpy((unsigned char *)&rs, rcv_buffer, sizeof(r));
2. 用Python实现的结构化数据处理
现在问题来了: 如果用Python, 还能够同样方便的实现上述的结构化数据处理吗? 也就是需要实现以下功能:
- 能够以变量名访问数据段, 不需要手动计算偏移量
- 能够处理bit级的数据段
- 能够方便的形成串行化通信字节流, 也能方便的从接收的字节流中解析数据;
有人可能觉得这不是问题: 用python的字典不是也能实现吗? 仔细想一想, 字典只能够提供第一种需求, 即以变量名访问数据段. 但python因为是高级语言, 整数只提供int一种数据结构, 而协议中很多时候数据段是bit级的, 或单字节, 两字节, 三字节的. 只用python原生的数据结构是不能直接访问bit级的数据段的, 甚至连数据体最后到底占了几字节, 都不能方便的统计.
为了解决这个问题, 本质还是要退回到C语言的级别来. 好在python提供了ctypes这个库, 能够让我们在python中实现类似C语言的功能.
>>> from ctypes import *
>>> class Req(Structure):_fields_=[('uRouter',c_ubyte,1),('uSubNode',c_ubyte,1),('uCM',c_ubyte,1),('uCD',c_ubyte,1),('uLevel',c_ubyte,4),('uChannel',c_ubyte,4),('uErrBate',c_ubyte,4),('uResBytes',c_ubyte),('uSpeed',c_ushort,15),('uUnit',c_ushort,1),('uReserve',c_ubyte)]
>>> r=Req()
>>> sizeof(r)
8
>>> r.uUnit=1
>>> print r.uUnit
1
>>> r.uUnit=2
>>> print r.uUnit
0
ctypes库的最主要作用其实是用于python程序调用c编译器生成的库和dll, 但我们这里只用到数据结构这一块.
ctypes在使用时有以下注意事项:
- 自定义的结构体类必须继承Structure或Union类;
- 自定义的结构体类中必须定义一个名为fields的列表变量, 其中每个元素是一个tuple, 定义了结构体每个数据单元信息, 格式是(‘变量名字符串’, 变量数据类型 [, 比特数])
- 定义了class后, 可以用sizeof(类名)查看数据体字节数, 和c语言一样. 然后用实例名.成员名进行相应数据单元的访问, 如果继承后定义了init()方法, 还可以进行类的初始化操作
3. 串行数据流处理
有了结构体, 上面的三条要求满足了俩个, 关于第三个要求, ctypes虽然提供了cast()方法, 但经过我研究, 发现cast其实只能实现简单的数组等结构的数据类型指针转换, 但无法像c那样将结构体对象地址转换成字节地址的. 这种情况下就需要python的另一个库:struct
struct是专门用于结构体与数据流转换的库, 我们用到的主要方法是pack()和unpack(). pack()的使用说明如下:
struct.pack(fmt, v1, v2, …)
Return a string containing the values v1, v2, … packed according to the given format. The arguments must match the values required by the format exactly.
举个例子:
>>> pack('BHB',1,2,3)
'\x01\x00\x02\x00\x03'
pack()的用法和format()很像, 第一个参数用一个字符串指明了要转换的格式, 例如’B’表示8位无符号整数, ‘H’表示16位无符号整数等等, 具体详见python帮助里关于struct库的说明. 这里的’BHB’就等于指明了, 将后面的三个数转成字节流, 第一个数以8位无符号数表示, 第二个以16位无符号数表示, 第三个以8位无符号数表示.
等等! 哪里不对啊? 两个8位无符号数, 一个16位无符号数, 加起来应该4个字节才对. 可是我们看转换结果’\x01\x00\x02\x00\x03’一共是五个字节, 最后一个3也被当16无符号数处理了, 难道是bug了?
这个问题其实在帮助文档里也说的很清楚了, 这是所谓machine’s native format和standard format的区别. 简而言之就是, 对于有些C编译器, 如果没有做特殊编译约束, 出于处理字宽的考虑, 对类似unsigned char这样的数据, 并非真的用1字节表示, 而是用处理时最适合cpu寄存器的长度表示, 比如跟在一个无符号16位数后面的一个无符号8位数, 就同样用16位位宽表示. 这样尽管浪费了内存, 但在寻址赋值等处理起来更有效率… 总而言之, 如果一定要求严格的8位和16位, 就需要使用standard format, 就是在格式字符串的首字母加以限定, 如:
>>> pack('>BhB',1,2,3)
'\x01\x00\x02\x03'
这里的>表示: 字节流转换使用standard format, 而且使用大端模式.
4. 结构体的字节流转换
有了pack()这个工具, 再回到前面的结构体字节流转换上… 发现还是有问题啊, 因为pack()可以实现单字节, 双字节, 却没法对bit field这种东西操作. 又该怎么解决呢.
其实这个问题, 我也没找到好的解决办法, 毕竟pack()需要我们手工一个个指定变量, 定义顺序和字节长度. 这里我提供一种解决方案, 那就是借用Union.
仍以前面的结构体为例, 换一种写法:
>>> class Flag_Struct(Structure):_fields_=[('uRouter',c_ubyte,1),('uSubNode',c_ubyte,1),('uCM',c_ubyte,1),('uCD',c_ubyte,1),('uLevel',c_ubyte,4)]>>> class Flag_Union(Union):_fields_=[('whole',c_ubyte),('flag_struct',Flag_Struct)]>>> class Channel_Struct(Structure):_fields_=[('uChannel',c_ubyte,4),('uErrBate',c_ubyte,4)]>>> class Channel_Union(Union):_fields_=[('whole',c_ubyte),('channel_struct',Channel_Struct)]>>> class Speed_Struct(Structure):_fields_=[('uSpeed',c_ushort,15),('uUnit',c_ushort,1)]>>> class Speed_Union(Union):_fields_=[('whole',c_ushort),('speed_struct',Speed_Struct)]>>> class Req(Structure):_pack_=1_fields_=[('flag',Flag_Union),('channel',Channel_Union),('uResBytes',c_ubyte),('speed',Speed_Union),('uReserve',c_ubyte)]
简而言之, 就是所有涉及bit-field的字段都用一个union和子struct来表示. (其中pack是为了1字节对齐, 原因与上一节介绍过的native format和standard format类似). 这样做的目的是为了折中比特字段访问与整字节的转化处理, 例如:
>>> r=Req()
>>> r.speed.speed_struct.uUnit=1
>>> r.flag.flag_struct.uLevel=0xf
>>> ack('>BBBHB',r.flag.whole,r.channel.whole,r.uResBytes,r.speed.whole,r.uReserve)
'\xf0\x00\x00\x80\x00\x00'
5. 一种更简单的字节流转化方法
后来通过仔细查看文档, 发现其实ctypes里提供了一种更简单的字节流转化方法:
string_at(addressof(r),sizeof(r))
addressof()和string_at都是ctypes里提供的方法. 这是最接近于原生c的处理方法, 这样连union都不用定义了
>>> class Req(Structure):_pack_=1_fields_=[('uRouter',c_ubyte,1),('uSubNode',c_ubyte,1),('uCM',c_ubyte,1),('uCD',c_ubyte,1),('uLevel',c_ubyte,4),('uChannel',c_ubyte,4),('uErrBate',c_ubyte,4),('uResBytes',c_ubyte),('uSpeed',c_ushort,15),('uUnit',c_ushort,1),('uReserve',c_ubyte)]>>> sizeof(Req)
6
>>> r=Req()
>>> r.uUnit=1
>>> r.uCM=1
>>> string_at(addressof(r),sizeof(r))
'\x04\x00\x00\x00\x80\x00'
如果需要大端的数据结构, 超类需要选择BigEndianStructure, 此时bit-field的定义也是从高到低的, 需要重新调整定义的顺序, 如下:
>>> class Req(BigEndianStructure):_pack_=1_fields_=[('uLevel',c_ubyte,4),('uCD',c_ubyte,1),('uCM',c_ubyte,1),('uSubNode',c_ubyte,1),('uRouter',c_ubyte,1),('uErrBate',c_ubyte,4),('uChannel',c_ubyte,4),('uResBytes',c_ubyte),('uUnit',c_ushort,1),('uSpeed',c_ushort,15),('uReserve',c_ubyte)]>>> r=Req()
>>> r.uLevel=0xf
>>> r.uUnit=1
>>> string_at(addressof(r),sizeof(r))
'\xf0\x00\x00\x80\x00\x00'
最后有人要问了: Python是一种高级语言, 为啥要做这么低级的事情呢? 其实术业有专攻, 对于嵌入式通信, 用python做高层的辅助测试工具是非常方便的.
(2015-12-14 补充)将字节流灌注到结构体中实现解析的方法:
r = Req()
s = io_rcv() #receive byte stream from io
memmove(addressof(r),s,sizeof(Req))
...
[Python]ctypes+struct实现类c的结构化数据串行处理相关推荐
- python爬虫 爬取360图片(非结构化数据)
爬虫思路:先拼接json数据包的url,再从中提取图片链接 域名:image.so.com 抓包 360图片是动态加载的数据 点击图片分类中的清新美女 --> ctrl + shift + i ...
- java能像python弄字典吗_python字典和结构化数据
5.1 字典数据类型 字典的索引可以使用许多不同类型的数据,不只是整数.字典的索引被称为"键",键及其关联的值称为"键-值"对,在代码中,字典输入时带花括号{} ...
- mysql 非结构化数据_hbase非结构化数据库与结构化数据库比较
目的:了解hbase与支持海量数据查询的特性以及实现方式 传统关系型数据库特点及局限 传统数据库事务性特别强,要求数据完整性及安全性,造成系统可用性以及伸缩性大打折扣.对于高并发的访问量,数据库性能不 ...
- 对于半结构化数据的讲解,这可能是最通俗易懂的一篇文章了
一. 概述 相对于结构化数据(即行数据,存储在数据库里,可以用二维表结构来逻辑表达实现的数据)而言,不方便用数据库二维逻辑表来表现的数据即称为非结构化数据,包括所有格式的办公文档.文本.图片.XML. ...
- 非结构化数据的相关知识
一.出现原因 信息社会化时代,各行各业在处理相关业务的过程中,都累计了海量的数据信息,随着IT应用的普及和发展,传统的纸质资料存储方式在不断缩减,更多的采用电子信息的存储方式存放在计算机中.这些信息数 ...
- 什么是结构化数据?什么是半结构化数据?
概述 相对于结构化数据(即行数据,存储在数据库里,可以用二维表结构来逻辑表达实现的数据)而言,不方便用数据库二维逻辑表来表现的数据即称为非结构化数据,包括所有格式的办公文档.文本.图片.XML.HTM ...
- 什么是结构化数据?什么是半结构化数据?(*)
概述 相对于结构化数据(即行数据,存储在数据库里,可以用二维表结构来逻辑表达实现的数据)而言,不方便用数据库二维逻辑表来表现的数据即称为非结构化数据,包括所有格式的办公文档.文本.图片.XML.HTM ...
- 结构化数据、半结构数据和非结构数据的总结
(一) (1)结构化数据(即行数据,存储在数据库里,可以用二维表结构来逻辑表达实现的数据) (2)非结构化数据,包括所有格式的办公文档.文本.图片.XML.HTML.各类报表.图像和音频/视频信息等等 ...
- 结构化数据和非结构化数据区别
结构化数据,即行数据,存储在数据库里,可以用二维表结构来逻辑表达实现的数据),而不方便或者无法采用数据库二维逻辑表来表现的数据即称为非结构化数据,包括所有格式的办公文档.文本.图片.XML.HTML. ...
最新文章
- Java 反射:框架设计的灵魂
- python调用C++之pybind11入门
- php重构ifelse,php - 重构条件语句PHP - SO中文参考 - www.soinside.com
- SpringMVC+RestFul详细示例实战教程(实现跨域访问)
- php cgi远程控制,php-cgi如何使用(php cli模式执行php文件)
- Django(part4)--练习及re_path方法
- .NET 类型(Types)的那些事
- 安卓开发之Toast(吐司)应用
- SCCM SP 1中文版安装前需要更新的内容-Part1
- 为什么我不再和别人比较了?
- ERP项目学习(一)
- ureport2项目使用
- Navision的ERP系统 - 微软Dynamics NAV的ERP软件评论
- Visio连接线的箭头如何变为直线、双箭头;直线转换为箭头
- PHP生成缩略图、加水印
- ibm服务器显示器接口,显示器接口类型怎么选,4种主流接口要了解
- Aspect Ratio Fitter 重温总结(多图)
- 华为摄像头 SDC REST 接口对接 经验
- 《最强大脑——77招让你成为脑力最好的人》读书笔记
- 【R语言】使用nnet过程中报错Error in eval(predvars, data, env) : object ‘naulong‘ not found
热门文章
- vue+elementui项目中遇到的坑/难题
- NPC_3level_Inverter:基于MATLAB Simulink的中性点钳位三电平逆变器仿真模型
- 8.3. Outlook Express
- MDict中最好用的英语、汉语词典
- 微信多开生成器PC端源码
- 基于JAVAWeb商铺租赁管理系统计算机毕业设计源码+数据库+lw文档+系统+部署
- WebMatrix网站开发系列教程:第一讲 WebMatrix入门经典
- 【JavaScript-进阶】详解数据类型,内存分配,API元素对象获取
- “艺术”与“技术”的碰撞------浅谈“艺工交叉”
- Unity PureMVC框架案例