struct.pack也就是将数据按照二进制的格式进行传输 | |
在网络编程中,利用 socket 进行通信时,常常会用到 struct 模块,在网络通信中,大多数传递的数据以二进制流(binary data)存在。传递字符串时无需过多担心,但传递 int,char 之类的基本数据时,就需要一种机制将某些特定的结构体类型打包成二进制流的字符串,然后在进行网络传输,而接收端也可以通过某种机制进行解包还原出原始数据。struct 模块便提供了这种机制,该模块主要作用就是对 python 基本类型值与用 python 字符串格式表示的 C struc 类型间的转化(This module performs conversions between Python values and C structs represented as Python strings.)。 另一种使用场景在与 python 与其他语言之间数据交换时使用,例如 C++ 写的客户端发送了一个 int 型(4字节)的数据到 Python 的服务器,Python 接收到这个数据时,需要解析成 Python 认识的整数,此时就将用到 struct 模块。 |
|
如果熟悉结构体中数据对齐的规则,可以合理设计结构体的结构,各成员变量的顺序,使得所有的数据成员存放在连续的存储区,而且结构体的长度等于所有成员长度之和(可以适当在尾部用字符数组补齐,避免编译器自动填充),这样就方便用send函数发送了。( 如果服务器和客户端都是用C/C++开发,两端可以通过同样结构的结构体来封包和解包,可以不考虑数据对齐的问题)下面讨论的是在C++和python开发的两端之间传输数据的情况:客户端用的C++编写,服务器端用python编写,相对于C++中用struct来封包和解包,python提供了struct库实现类似的功能,最重要的三个函数是pack,unpack,calcsize,struct库处理二进制数据的具体用法可以参考这篇文章:http://www.cnblogs.com/gala/archive/2011/09/22/2184801.html。在python下用socket接收到的字节流实际上是字符串,需要对所有的字节进行指定解析格式,所以在C++发送的时候就要避免发送不确定的数据(如编译器自动填充的数据),如果用struct组织数据就需要考虑数据对齐的问题了。下面用一个实例来说明:在C++客户端有1个a(float) ,1个b(unsigned char),一个c(short) 要发送给服务器端,在不使用#pragma指令指定编译器的对齐位数时, 在我的编译器环境下默认为4,可以这样设计结构体: 另一个考虑的问题就是进制的转换 |
#!/usr/bin/env python
#coding:utf-8
__author__ = 'ferraborghini'
from socket import *
import struct
#将16进制数据当做字节流传递
def dataSwitch(data):
str1 = ''
str2 = ''
while data:
str1 = data[0:2]
s = int(str1,16)
str2 += struct.pack('B',s)
data = data[2:]
return str2
if __name__ == "__main__":
HOST = 'localhost'
PORT = 21567
BUFSIZE = 1024
ADDR = (HOST,PORT)
tcpCliSock = socket(AF_INET,SOCK_STREAM)
tcpCliSock.connect(ADDR)
while True:
data = raw_input('>')
if not data:
break
tcpCliSock.send(dataSwitch(data))
data = tcpCliSock.recv(BUFSIZE)
if not data:
break
print data
tcpCliSock.close()
解析报文,这时候读入的其实相当于二进制流,我们要做的就将二进制流转化为16进制就行。
#!/usr/bin/env python
#coding:utf-8
from socket import *
from time import ctime
if __name__ == "__main__":
HOST = '' #此处为空代表可以绑定所有有效地址
PORT = 21567
BUFSIZE = 1024
ADDR = (HOST,PORT)
tcpSerSocket = socket(AF_INET,SOCK_STREAM)
tcpSerSocket.bind(ADDR)
tcpSerSocket.listen(5) #最多可以有5个连接同时进入
while True:
print 'waiting for connection...'
tcpCliSock,addr = tcpSerSocket.accept()
print '...connected from:',addr
while True:
data = tcpCliSock.recv(BUFSIZE)
print data.encode('hex')
if not data:
break
tcpCliSock.send('[%s] %s'%(ctime(),data))
# tcpCliSock.close() #如果接收完,就断开的话,下次再发送就会报错,书本上有问题
tcpSerSocket.close()
项目开发中的一个例子:控制的是USR-R16-T继电器灯的通断
#将16进制数据当做二进制字节流传递
def dataSwitch(data):
str1 = ''
str2 = b''
while data:
str1 = data[0:2]
print(str1)
s = int(str1,16)
str2 += struct.pack('B',s)
data = data[2:]
return str2
'''
req = struct.pack('7B', int('61', 16), int('64', 16), int('6D',16), int('69',16), int('6E',16), int('0D',16), int('0A',16)) #将16进制数据转换为10进制,再打包
client.send(req)
data = client.recv(8096)
print ('Server send information : %s' % data.decode('utf-8'))
ss = dataSwitch('61646D696E0D0A')
print(ss)
client.send(ss)
data = client.recv(8096)
print ('Server send information : %s' % data.decode('utf-8'))
'''
#从终端获取16进制字符串,然后转换为二进制的字节流用于网络传输
while True:
msg = input('-->').strip()
print(type(msg))
if len(msg)==0:continue
client.send(dataSwitch(msg))
#print(dataSwitch(msg))
#client.send(msg.encode('utf-8'))
#client.send(msg)
data = client.recv(8096)
print(data)
#print ('Server send information : %s' % data[0:2].decode('utf-8'))
sk_obj.close()