目录
1、缘起:文本传输协议ME
1.1数据安全
1.2 一个简单的自定义网络通信协议ME
1.2.1 一个简单的基于ME的网络通信程序
1.2.2 ME协议及核心代码
2、struct
2.1 struct解决的问题
2.2 struct解决问题基础
2.2.1 函数
2.2.2 数据类型
3、struct格式字符串
3.1 字节序、大小与对齐
3.2 格式字符串fmt
3.2.1 格式字符与数据类型
3.2.2 特殊事项
3.2.3 格式字符计数
3.3 struct应用示例
3.3.1 100字节ME包
3.3.2 3个整数
3.3.3 解封装到对象
3.3.4 对齐与填充①
3.3.5 对齐与填充②
在网络通信应用中,有时候要考虑业务数据安全,谁会希望自家机密级业务数据常常成为他人机器学习样本而导致辛辛苦苦摸索多年的核心业务/商业模式轻易地为人一朝所获呢?可以断言,业务的信息化程度越高,数据安全程度就应越高。
数据有多种属性,如公开数据、隐私数据。数据分等级,如802.11协议帧分管理帧、控制帧、数据帧;有授权对象,不是销售总监就不必看季度销售财报,不是主任就不必知晓主任级别文件内容——这种职场中严控得非常好的事务,在网络上当然不应逊色。对数据的管理,一般基于以数据属性为基础的数据模型。管理班级学生,如果是学生成绩管理,只须基于学号、课程号、分数,而每天几点几分进入学校(到校时间)与此无关。
那么如何加强数据安全?
最简单、成本最低的安全手段莫过于加密与自定义通信协议。建立一条独立的物理或逻辑通信路径当然可以,但成本大太多。
数据安全自然有其大道:“不知道”。
“不知道”的含义指“不知道目标、不知道数据和不知道意义”。
在公用网络上通信而不想数据被任何第三方捕获几乎是不可能的事,特别是自古以来咱自带势力意识(平台观),稍具规模组织也有着强烈情报意识,即是说,既有观念又有技术(钱),让他人看不到并不容易实现,那么,只能走另一途径:看到而不知道。
PS:专利之路是一种路径,一种共赢之路,共赢之路有很多,未必一定得建立在窃取核心机密之上。当然也可以在他人的“心”上建立一套观念/心态,但我压根就不认为这能有多可靠(虽然网络上有很多心理、情感分析的优质内容输出视频,这说明网络仍是那片热心群众甚多且自带诸葛人设的网络,但经不起推敲的是,此类分析大多属于细节预先设定外加马后炮式的理想分析——对于很多人,能不能站到上帝视角是很大一回事,而很小一回事的及时从感知觉这种认知层面察觉到相关信息也要难倒一大批人——不过倒是强化了我的一种意识:凡事不能只分析事,也要分析人;再者,如我那篇代码调节中所述,客观虚拟性……)。当然也可以通过其他机制保证这条路只有自己能走而他人不行,但这得有实力且相对性脆弱或代价不小。
“看到不知道”无非三层含义:量多,加密,无认知。
量多,如二十万条记录,五百个画面等,当第三方“看到”的内容太多,在机器学习还未出模型前,人工分析不了;
加密,第三方“看到”的数据是明明白白且有条有理的乱码。加密仅仅是通过加密算法将原始数据(有意义数据)呈现为加密数据(无意义数据)吗?也不见得:变换一种数据呈现模式,同样实现“加密”效果;
无认知,相当于甲和乙在一群日本学生中用法语聊得有滋有味。自定义通信协议就是一片“搜嘎”“索滴斯类”“撒油啦啦”中甲乙之间的一声“Bonjour”,即用不同的模式进行交流,或“沟通不同频”。量多与加密属于“无认知”的具体类别,但无认知范围更广,可指任何非常规模式。在通信模式未被识别之前,通信内容是保密的;而只要数据足够多,通信模式一定可被识别,所以,通信协议应够复杂、可变、可升级版本,保证模式被识别的时间大于模式更新周期。
我定义一个简单的文本传输协议ME,服务器与客服端通过ME协议进行文件传输。服务器与客户端通信很简单:客户端向服务器发送文件传输请求,服务器检查该请求,若可行则将对应文件传输给客户端。
主程序:
import threading,time import server import client if __name__=="__main__": s=threading.Thread(target=server.main) c=threading.Thread(target=client.main,args=('server.txt',)) s.start() time.sleep(2) c.start()服务器:
客户端:
我设定请求的文件必须是utf-8编码或被utf-8编码兼容的文件。
ME工作在应用层,用于服务器和客户端间的文件传输与文件传输时的通信控制。
ME数据包格式:
Ver=1,ME版本号。
TLEN:实际数据长度。
Typ:ME数据包类型,DC=0代表通信控制包,DD=1代表通信数据包。
Seed:相对的数据首地址。
data:传输的数据。数据(单位:字节)以链式结构分散存放在data空间中。
ME数据包总长100字节,Ver、TLEN、Typ、Seed各占1字节,data占96字节。data中的数据采取“链式”存放方式,“指针”占一个字节,所以一次最多传送24个字符(全utf-8中文字符,实际上为支持存储空间分配管理,我设定一次传输15-20个字符),数据结束标记为0xFF。data中的非通信“空间”(无数据存放的位置)随机填充数据。下面是两个ME数据包内容输出示例。
例1、内容为“test.txt”的通信控制包
ME协议下的“test.txt”:
b'\x01\x08\x00\x9c0\x02+]c\x18N\x1b\x13!\x0cH2]`PD\\Gr\rNQ^test.txt\xffnA";u\x10,\x18^5zC1\x0e\x7f[lZ\ry\x16\x05t\x11c\x1b!\x18A\x0e\x0e\x0c&m\x10Q9\x1co4e\x05\x05\x07$s4\x1e1\t/\\D\x15j1\n.\x1dJP`-'
网络输出:
①数据包捕获设置
C:\Users\cyx>windump -vv -A -i 2 ip host 192.168.1.104 and 192.168.1.1
②构造并发送数据包
③数据包捕获结果
例2、内容为“how are you”的通信数据包
ME协议下的“how are you”:
b'\x01\x0b\x01\xda@r\xca\x1b\x00mKL\x1ar\x13A\x13ar?/w74>/i\x14y\xcfF"\\\x1d(n,\r*\x0bu\xff\x00Xo\xe2\x1c\n"D\x13=nVd"Q\\-eYd,Ha\x85\x1f\nUl/ \x9cYe\xc77;"o\xa8"nGyc1~t2h\xac)\x01W. \xc0w\xe0'
网络输出:
①数据包捕获设置
C:\Users\cyx>windump -vv -A -i 2 ip host 192.168.1.104 and 192.168.1.1
②构造并发送数据包
③数据包捕获结果
ME协议接口:
ME协议核心方法pack:
def pack(self,data,type=1):#假定data(中文或英文)未经过utf-8编码的str from random import randint,choice packet=[b'`' for i in range(100)] indset=[i for i in range(4,100)] packet[0]=1 packet[1]=len(data) packet[2]=type pnext=3 if type==self._DC: #通信控制包将直接存放数据:OK或ERROR或英文文件名 ldata=data.encode('utf-8') if len(data)!=len(ldata): raise ValueError('PACK-->value error.') seed=randint(4,80) packet[pnext]=chr(seed | 0x80).encode('ISO/8859-1') for i in range(len(ldata)): packet[seed+i]=chr(ldata[i]).encode('utf-8') indset.remove(seed+i) pnext=seed+len(ldata) packet[pnext]=b'\xFF' indset.remove(pnext) else: if len(data.encode("utf-8"))+len(data)>96: raise ValueError("PACK-->value error.") for i in data: enc=i.encode('utf-8') index=choice(indset) tindset=indset[:] if len(enc)==1: while index+1 not in tindset and tindset: tindset.remove(index) index=choice(tindset) if tindset==[]: raise ValueError("PACK-->no splot.") packet[pnext]=chr(index | 0x80).encode('ISO/8859-1') pnext=index+1 packet[index]=enc indset.remove(index) indset.remove(index+1) else: while not (index+1 in tindset and index+2 in tindset and index+3 in tindset) and tindset: if index-1 not in tindset and index+1 not in tindset: tindset.remove(index) index=choice(tindset) if tindset==[]: raise ValueError("PACK-->no splot.") packet[pnext]=chr(index & 0x7F).encode('ISO/8859-1') pnext=index+3 for j in range(len(enc)): packet[index+j]=chr(enc[j]).encode('ISO/8859-1') for j in range(index,index+4): indset.remove(j) packet[pnext]=b'\xFF' for i in indset:packet[i]=chr(randint(0,127)).encode('utf-8') rdata=b'' for i in range(4,100): rdata+=packet[i] spacket=pack('>3Bc96s',packet[0],packet[1],packet[2],packet[3],rdata) return spacketME协议核心方法unpack:
def unpack(self): draw=[] f=False gnext=self.gnext pnext=self.Seed while pnext!=b'\xFF'[0]:#指针域占1字节,最高位标识数据类型,其余7位标识数据位置 n=gnext(pnext,False) if len(draw)>self._LEN or not (0<=n<100): #结束标志丢失 f=True break if gnext(pnext): #最高位1标识单字节数据,0标识多字节数据 if self.Typ==self._DC: while self.R[n]!=b'\xFF'[0]: draw.append(chr(self.R[n])) n+=1 break else: draw.append(chr(self.R[n])) pnext=self.R[n+1] else: draw.append(self.R[n:n+3].decode('utf-8')) pnext=self.R[n+3] if f: draw[0]=0x80 self.iter=draw在上面的ME协议中,如果data空间里的数据不是链式结构,那么将多个数据项封装成bytes还有更方便的方法:struct。
处理字节级数据,除之前介绍的在python对象和c数据类型间架起友谊桥梁的ctypes外,struct是一个不错的选择,特别是在网络通信方面。
内存单元可抽象为一个访问受限的一维数组,就其本身而言,眼里只有“有数据”和“无数据”,目光长远的话,还有“连续数据”和“非连续数据”。但对代码而言,数据是多姿多彩的:整数、小数、字符……那么,在内存单元数据和数据类型间存在一个匹配问题,也即类型-字节模式识别问题。一般情况下,这个问题由编译器解决,但是,在一些特殊应用情境,必须由代码自身解决。struct能提供非常方便的解决方案。
struct能在bytes和封装过的二进制数据(c struct)间进行双向“翻译”,即,struct能在python数据值(python对象的值)和以bytes为载体的c struct间进行转换。这就使得struct能用于来自文件、网络以及其他来源的字节级数据处理或者更一般的二进制数据处理,如解决字符编码、通信协议、代码/信息安全等方面问题。
具体而言,当准备好待转换数据和存放结果的目标对象后,struct便可通过一个非常简洁的格式字符串(以下简称为“格式串”)来准确描述python数据值如何对应到c struct在bytes的布局(从哪里开始,几个字节)。
>>> from struct import * >>> a='你好啊' >>> s=b"" >>> s+=pack('>9s',a.encode()) >>> b=unpack('>9s',s) >>> b[0].decode() '你好啊' >>>默认情况下,对一个指定的c struct进行封装的结果中将包含填充byte以便维持所涉及的c数据类型的正确对齐,类似的,在解封装的时候对齐byte被计入在内。之所以这样做是为让c struct在封装后的bytes中精准地对应于该c struct在内存中的布局。处理平台无关数据格式或限制隐式填充byte,则考虑的应是标准大小和对齐而不是本地大小和对齐。
部分struct函数或Struct方法带有参数buffer。buffer指向实现Buffer协议且提供可读或可读写内存块的对象,大部分情况下此类对象是bytes或bytearray实例,但很多其他实现Buffer协议的类型可被视为bytes数组,因而能够直接对它们进行读写而无需从bytes对象进行额外拷贝。
struct定义struct.error用于处理相关异常。
>>> raise struct.error('socket-data error.') struct.error: socket-data error. >>>struct.pack(fmt, v1, v2, ...):根据格式串fmt将数据参数v1、v2……封装成一个bytes对象并返回。数据参数的类型必须严格匹配fmt。
>>> bp=pack('>hiqi',p1.x,p1.y,p1.z,p1.p) >>> bp b'\x00\x01\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04' >>>struct.pack_into(fmt, buffer, offset, v1, v2, ...):基本等同pack,但封装后的结果存放在buffer中offset指定的位置。buffer从offset开始的数据空间至少为calcsize(fmt)。
>>> buf=create_string_buffer(b'-'*sizeof(p1)) >>> buf.raw b'------------------------\x00' >>> pack_into('>hiqi',buf,0,p1.x,p1.y,p1.z,p1.p) >>> buf.raw b'\x00\x01\x00\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\x03\x00\x00\x00\x04------\x00' >>>struct.unpack(fmt, buffer):根据fmt从buffer(假定由pack(fmt,…)得到)解封装,结果是一个包含各数据项的元组(即便只有一个数据项)。buffer大小(单位:字节)必须匹配fmt,也即等同calcsize(fmt)得出的大小。
>>> r=unpack('>hiqi',buf[0:18]) >>> r (1, 2, 3, 4) >>>struct.unpack_from(fmt, buffer, offset=0):基本等同unpack,但将从buffer指定位置offset开始解封装。buffer从offset开始的数据空间至少为calcsize(fmt)。
>>> buf=create_string_buffer(b'-'*sizeof(p1)) >>> pack_into('>hiqi',buf,7,p1.x,p1.y,p1.z,p1.p) >>> r=unpack_from('>hiqi',buf,7) >>> r (1, 2, 3, 4) >>> pack_into('>hiqi',buf,3,p1.x,p1.y,p1.z,p1.p) >>> unpack_from('>hiqi',buf,3) (1, 2, 3, 4) >>>struct.iter_unpack(fmt, buffer):基本等同unpack,但不会返回元组,而是返回一个迭代器,该迭代器的内容便是从buffer解封装后的各数据项。同样的,buffer大小必须匹配fmt。
迭代器的每次访问都会返回一个元组,元组元素为格式串确定的数据项。
>>> buf=pack('>hiqi',p1.x,p1.y,p1.z,p1.p) >>> for i in iter_unpack('>hiqi',buf):print(i) (1, 2, 3, 4) >>> a=iter_unpack('>hiqi',buf) >>> a <unpack_iterator object at 0x0000028DD47F7048> >>> next(a) (1, 2, 3, 4) >>>struct.calcsize(fmt):返回根据fmt确定的struct大小或pack(fmt,…)返回的bytes对象大小。
>>> calcsize('>hiqi') 18 >>> calcsize('hiqi') 20 >>>class struct.Struct(format):struct模块定义的一个类,返回一个根据格式串format读写二进制数据的Struct对象。Struct提供上述函数除struct.calcsize(fmt)外的同名方法:
pack(v1, v2, ...) pack_into(buffer, offset, v1, v2, ...) unpack(buffer) unpack_from(buffer, offset=0) iter_unpack(buffer)以及实例属性format、size,类似定义如下:
>>> class Struct: def __init__(self,fmt): self.format=fmt self.size=calcsize(fmt) def pack(self,*args):... def pack_into(self,buffer,offset,*args):... def unpack(self,buffer):... def unpack_from(buffer,offset=0):... def iter_unpack(self,buffer):... >>>Struct对象一旦创建,当fmt相同时,调用该对象方法比调用同名函数要高效(fmt只须编译一次)。
Struct对象方法与struct同名函数无论从功能还是从参数要求、用法上看都几乎一致(以_struct.c中struct.pack和struct.Struct.pack源码为例作对比):
PyDoc_STRVAR(pack_doc, "pack(fmt, v1, v2, ...) -> bytes\n\ \n\ Return a bytes object containing the values v1, v2, ... packed according\n\ to the format string fmt. See help(struct) for more on format strings."); static PyObject * pack(PyObject *self, PyObject *args) { PyObject *s_object, *fmt, *newargs, *result; Py_ssize_t n = PyTuple_GET_SIZE(args); if (n == 0) { PyErr_SetString(PyExc_TypeError, "missing format argument"); return NULL; } fmt = PyTuple_GET_ITEM(args, 0); newargs = PyTuple_GetSlice(args, 1, n); if (newargs == NULL) return NULL; s_object = cache_struct(fmt); if (s_object == NULL) { Py_DECREF(newargs); return NULL; } result = s_pack(s_object, newargs); Py_DECREF(newargs); Py_DECREF(s_object); return result; } PyDoc_STRVAR(s_pack__doc__, "S.pack(v1, v2, ...) -> bytes\n\ \n\ Return a bytes object containing values v1, v2, ... packed according\n\ to the format string S.format. See help(struct) for more on format\n\ strings."); static PyObject * s_pack(PyObject *self, PyObject *args) { PyStructObject *soself; PyObject *result; /* Validate arguments. */ soself = (PyStructObject *)self; assert(PyStruct_Check(self)); assert(soself->s_codes != NULL); if (PyTuple_GET_SIZE(args) != soself->s_len) { PyErr_Format(StructError, "pack expected %zd items for packing (got %zd)", soself->s_len, PyTuple_GET_SIZE(args)); return NULL; } /* Allocate a new string */ result = PyBytes_FromStringAndSize((char *)NULL, soself->s_size); if (result == NULL) return NULL; /* Call the guts */ if ( s_pack_internal(soself, args, 0, PyBytes_AS_STRING(result)) != 0 ) { Py_DECREF(result); return NULL; } return result; }S.pack为Struct对象的pack方法,pack函数实际上会调用S.pack,而S.pack最终会调用s_pack_internal。
>>> from struct import * >>> p1=point(1,2,3,4) >>> buf=create_string_buffer(b'-'*sizeof(p1)) >>> sobj=Struct('>hiqi') >>> sobj.pack_into(buf,3,p1.x,p1.y,p1.z,p1.p) >>> sobj.unpack_from(buf,3) (1, 2, 3, 4) >>>在c数据和python数据之间进行转换时明确给出类型上的对应方式,这是struct格式串存在的意义。
struct格式串在函数中由参数fmt指定,由控制字节序、大小、对齐的首字符和封装/解封装时指定数据类型的后续字符共同构成。
字节序主要特定于不同主机、不同平台间数据通信情境,即只有当代码需要处理来自特定源(如网络)的数据,并且只有那些一个数据单位需要多字节才能被完整描述的数据对象需要考虑字节序。字节序分大端序(big endian)和小端序(little endian)两种,大端序数据低地址存放高位数据,小端序数据低地址存放低位数据。
对于逻辑上相邻的数据,下一个数据起始地址编号是谁的倍数由“对齐”来指定。对齐指“内存对齐”,其作用一是解决不同平台对内存单元访问要求不一致问题,二是解决一些边界内存单元的读取需处理器两次访问内存的问题。
默认情况下c类型的字节序、大小和对齐都是本地(native)的,由编译器决定。
格式串第一个字符可用于指定封装数据的字节序、对齐和大小:
“@”为默认的第一个格式串字符。
本地字节序是大端序还是小端序取决于主机系统。
>>> import sys >>>from struct import * >>> sys.byteorder 'little' >>> buf=pack('I',1024) >>> buf b'\x00\x04\x00\x00' >>> buf=pack('@I',1024) >>> buf b'\x00\x04\x00\x00' >>>本地大小和对齐可由c编译器sizeof表达式确定,在代码中总是和本地字节序一起被考虑。
标准大小只能由格式字符决定。
“@”和“=”的区别:都使用本地字节序,但后者指定的大小和对齐是标准大小、对齐。
当无法确定接收到的网络数据是大端序还是小端序时可以使用“!”。
除了根据需要选择合适的“<”或“>”来指定(或强制改变为)非本地字节序外别无他法。
注意:
“填充”将因对齐而自动发生在c struct连续成员之间,而不是已经封装好的结构首或结构尾。
如果使用非本地大小和对齐(如“<”“>”“!”“=”),则不加入填充。
为使c struct尾满足特定类型的对齐要求,可以用带“0”(填充值)的该类型格式字符作为格式串结尾。
>>> buf=pack('llh',1,2,3) >>> buf b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00' >>> buf=pack('llhq',1,2,3) struct.error: pack expected 4 items for packing (got 3) >>> buf=pack('llh0l',1,2,3) >>> buf b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00' >>> buf=pack('llh0q',1,2,3) >>> buf b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00' >>> buf=pack('llh000000q',1,2,3) >>> buf b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00' >>>1.“?”对应C99定义的_Bool,如果这种类型不可用则相当于char,标准模式下总是1个字节。
2.“q”和“Q”在标准模式下总是可用,但在本地模式下仅当平台c编译器支持c long long或windows __int64时才可用。
3.当试图用任意整数格式代码封装一个非整数时,如果该对象有__index__()方法则在封装前调用__index__()转换成一个整数。
4.“n”和“N”只对本地大小(起始字符为默认或指定为“@”的fmt)可用。对于标准大小则需使用其他合适的整数格式字符。
5.“f”“d”“e”封装的数据分别代表符合IEEE 754标准的32位、64位、16位二进制格式,而不管浮点数所在平台。
6. 格式字符“P”只对本地字节序(起始字符为默认或指定为“@”的fmt)可用,而字节序字符“=”选择小端序还是大端序取决于主机系统,struct模块不会将“=”直接解析为本地字节序,所以此时“P”不可用。
>>> pack('=P',0x12345) struct.error: bad char in struct format >>> pack('P',0x12345) b'E#\x01\x00\x00\x00\x00\x00' >>>7.16位二进制类型“e”(半精度类型)在2008版 IEEE 754标准中被引入。它由1个符号位,5个指数位以及11个精度位(显式存储10位)组成,最大范围6.1e-05~6.5e+04。支持这种类型的c编译器并不多,在某种特定机器上,可以通过unsigned short存放数据(但不用于数学计算)。
一个格式字符可接在某个指定重复次数的数字,即“计数”,之后。例如,格式串“4h”和“hhhh”意义相同。
格式串中出现的空白字符无意义,因此数字与格式字符之间不能出现空白字符。
>>> pack('4h',1,2,3,4) b'\x01\x00\x02\x00\x03\x00\x04\x00' >>> pack('4 h',1,2,3,4) struct.error: bad char in struct format >>>“s”之前出现的数字不像其他格式字符那样代表重复计数,而是指一个bytes的长度。例如,“3s”指1个长度为3字节的bytes,而“3c”则指3个“c”(“ccc”)。如果未给出数字则默认为1。
封装时(packing),字符串会被截断或被空字节(“\x00”)填充以满足指定计数。而对于解封装(unpacking),解封bytes对象总是与计数精确一致。
>>> pack('10s',b'aa') b'aa\x00\x00\x00\x00\x00\x00\x00\x00' >>> pack('3s',b'aaaaaa') b'aaa' >>> unpack('3s',b'aaa') (b'aaa',) >>> unpack('3s',b'aaaa') struct.error: unpack requires a buffer of 3 bytes >>>当通过任意一个整型格式字符('b', 'B', 'h', 'H', 'i', 'I', 'l', 'L', 'q', 'Q')封装数字x时,如果x超出该格式字符合法值范围则抛出异常struct.error或DeprecationWarning。
格式字符“p”对“Pascal string”(pascal字符串)编码,这意味着一个长度可变的短字符串存放在定长(计数数字指定)bytes中:第一个byte存放字符串长度或255(这就是为什么是“短字符串”的原因),接着存放字符串中的字符。如果传给pack的与“p”对应的字符串太长(远远大于计数减1),则只存放前“计数减1”个字节的字符;如果字符串长度比“计数减1”小,则在剩余位置填充空字节以使封装后的该部分整体大小为计数值。在unpack()时,“p”将对应(消耗)“计数”(pack时指定)个字节,所以pack时该部分字符串决不能超出/少于“计数”或255字节。
>>> pack('5p',b"1234") b'\x041234' >>> pack('5p',b"12") b'\x0212\x00\x00' >>> pack('5p',b"1234567") b'\x041234' >>> unpack('5p',b"1234567") struct.error: unpack requires a buffer of 5 bytes >>> unpack('5p',b"12") struct.error: unpack requires a buffer of 5 bytes >>> unpack('5p',b"12345") (b'2345',) >>>格式字符“?”在unpack()中返回True(对于任意非0值)或False(0值),而在pack()则封装的是对应参数对象的“真值”(bool(obj),本地或标准模式下的布尔表示为0或1)。
格式字符的次序与整个fmt大小有着紧密联系,因此满足对齐要求所需的填充将因次序不同而不同:
>>> pack('ci', b'*', 0x12131415) b'*\x00\x00\x00\x15\x14\x13\x12' >>> pack('ic', 0x12131415, b'*') b'\x15\x14\x13\x12*' >>> calcsize('ci') 8 >>> calcsize('ic') 5 >>>如前所述,可以指定c struct尾填充类型:
>>> pack('llh0l', 1, 2, 3) b'\x01\x00\x00\x00\x02\x00\x00\x00\x03\x00\x00\x00' >>>对齐与填充只有在本地模式下才有效,标准大小与对齐下将不会生效。
更多关于struct版本详情请参考:https://docs.python.org/release/3.6.5/library/struct.html