最近基于Python做一个客户端,本身自己对Python不太熟悉,而且服务器是使用protobuf协议进行通信,之前主要是使用xml协议做通信,对protobuf也不了解,当然protobuf与xml类似,都有树形结构,而且貌似效率高些。对protobuf的Python使用有很多教程,其实主要来源于对官方文档的翻译,下面放出链接:
https://developers.google.com/protocol-buffers/docs/pythontutorial?hl=zh-CN
protobuf在使用中需要将定义的.proto文件转换为.py文件,使用中将文件导入做消息的填充和读取。常规的方法需要安装protobuf,其实只要做文件转换的话可以更简单的操作。在官方的release版本下面找到protoc-3.X.X-winXX.zip,然后解压后找到protoc.exe,这是一个在windows下可执行文件,可以直接完成转换操作。
然后就可以在cmd界面使用protoc命令进行转换,命令格式为:
protoc -I=源地址路径 --python_out=目标地址路径 源地址路径/xxx.proto
python中protobuf文件的使用教程很多,主要步骤是将文件导入,利用文件对象初始化消息,然后进行消息填充。
message = Message_pb2.Message() #新建信息
对于枚举类型,可以直接调用使用,例如:
define:
enum MSG_TYPE {
Message_Error = 0;
Login_Request = 1;
Login_Response = 2;
Heartbeat_Request = 3;
Heartbeat_Response = 4;
ConnectivityCheck_Request = 5;
ConnectivityCheck_Response = 6;
}
python:
message.msg_type = Message_pb2.Heartbeat_Request
对于多层消息的填充,python使用比较简单,基本直接调用,如:
login = message.request.login
login.username = "jiang"
login.password = "123456"
login.host_name = hostname
repeated类型的字段貌似要用add方法添加,没有使用过-_- 。
还有一类是map类型的,我是这样填充的:
define:
message HeartbeatRequest
{
map ap_maps = 1;//key是ssid,每个ssid取一个最强的(暂时考虑)
string ssid = 2;//当前正在监控的ssid
}
message ApInfo{
string ssid = 1;
string bssid = 2;
string signals = 3;
string channel = 4;
}
python:
heartbeat = message.request.heartbeat
heartbeat.ap_maps[k].ssid=v.ssid
heartbeat.ap_maps[k].bssid =v.bssid
heartbeat.ap_maps[k].signals = str(v.signal)
heartbeat.ap_maps[k].channel = str(self.getChannel(v.freq))
另外还有一个问题就是protobuf通信时通信双方并不知道消息的大小,假如不加处理的话,通信双方会出现粘包或者包断掉的问题,一般处理是加上消息头,消息定义一般是由len_pb_data和len_msg_name、msg_name、pb_data,len_pb_data和len_msg_name分别表示有效负载大小(消息序列化后长度)和消息名长度,msg_name表示消息名来唯一标识消息,pb_data就是序列化后的消息。消息头的处理当然可以自己定义,但是为了与其他端进行通信最好还是采用官方提供的方法,但是python官方文档对此的说明不多,最后还是阅读源码找到了google.protobuf.internal里面的encoder和decoder来处理消息头,具体处理如下:
from google.protobuf.internal import encoder, decoder
#封装message
def send(self, msg):
'''pack request to delimited data'''
data = msg.SerializeToString()
data = encoder._VarintBytes(len(data)) + data
self.s.send(data)
#解出message数据
def unpack(self):
buffer=[]
'''unpack from delimited data'''
data=self.s.recv(1024)
size, position = decoder._DecodeVarint(data, 0)
if((position + size)>1024):
buffer.append(data)
d=self.s.recv(position + size-1024)
if d:
buffer.append(d)
fulldata = b''.join(buffer)
message = Message_pb2.Message()
message.ParseFromString(fulldata)
return message
else:
message = Message_pb2.Message()
message.ParseFromString(data[position:position + size])
return message
这里主要调用了encoder里面的_VarintBytes方法和decoder里面的_DecodeVarint方法,我这里为了防止消息过长被截断,还做了多次读取的准备,有更好的方法欢迎交流,至于消息头具体怎么加上的方法可以自己去阅读源码,我这里就不分析了。