Protobuf介绍
xml、json是目前常用的数据交换格式,它们直接使用字段名称维护序列化后类实例中字段与数据之间的映射关系,一般用字符串的形式保存在序列化后的字节流中。消息和消息的定义相对独立,可读性较好。但序列化后的数据字节很大,序列化和反序列化的时间较长,数据传输效率不高。
Protobuf和Xml、Json序列化的方式不同,采用了二进制字节的序列化方式,用字段索引和字段类型通过算法计算得到字段之前的关系映射,从而达到更高的时间效率和空间效率,特别适合对数据大小和传输速率比较敏感的场合使用。
Protobuf作为数据交换的格式,广泛应用在网络编程中作为数据交换的格式使用。protobuf作为数据交换的格式,可以简化发送端序列化、接收端发序列的流程,方便将更多精力放在程序逻辑的处理而不是发送接收的数据处理。
因为protobu经常使用在网络编程中,下面就以一个简单的CS程序的处理流程,来简单介绍下在Python下使用Protobuf。
下载Protobuf与生成py文件
程序中使用到是message.proto
文件,具体内容如下:(proto文件的具体语法这里不再介绍)
syntax = "proto3";
message MsgPlayerLoginRequest
{
string playerName = 1;
string playerPass = 2;
string ip = 3;
int32 id = 4;
}
要将message.proto
文件装换成对应的py文件,需要使用protoc.exe
。下载地址如下:
protocolbuffers/protobuf
根据自己需求下载匹配的版本:
下载完成后解压缩,然后将bin目录中的protoc.exe
,放入到message.proto所在目录
。
然后使用命令:protoc --python_out=./ message.proto
,则在当前目录下生成message_pb2.py
文件,这就是python程序能够使用的文件。
也可以将protoc.exe
所在目录加入到环境变量Path中以便在各处都能使用。
protoc命令中,--python-out
指定输出文件的路径,message.proto
则是需要转换的目标文件。
工程程序
客户端连接服务端,连接成功后,构造MsgPlayerLoginRequest
,然后序列化后发送到服务端。服务端收到连接,然后接收数据,将其反序列化为MsgPlayerLoginRequest
并打印其中的字段值。其中用到的常用的序列化和反序列的成员函数:SerializeToString()
与ParseFromString()
。
import socket
import struct
from message_pb2 import MsgPlayerLoginRequest
class TestClient:
def __init__(self, ip_addr, port):
self.ip_addr = ip_addr
self.port = port
self.sock = socket.socket()
def start(self):
print('connect to {}:{}'.format(self.ip_addr, self.port))
self.sock.connect((self.ip_addr, self.port))
print('connect OK')
# 构造MsgPlayerLoginRequest
msg_login = MsgPlayerLoginRequest()
msg_login.playerName = 'hello hi'
msg_login.playerPass = '123456'
msg_login.ip = '127.0.0.1'
msg_login.id = 1
# 将SerializeToString对象序列化成字节流并发送出去
send_msg = msg_login.SerializeToString()
self.sock.sendall(send_msg)
print('send MsgPlayerLoginRequest ok')
if __name__ == '__main__':
one_client = TestClient('127.0.0.1', 10088)
one_client.start()
test_server.py
import socket
import struct
from message_pb2 import MsgPlayerLoginRequest
# 接收固定长度data_len的数据
def recv_some(client_sock, data_len):
recv_data = b''
remain_len = data_len
while True:
try:
cur_data = client_sock.recv(remain_len)
except ConnectionResetError as error:
return False, recv_data
recv_data += cur_data
remain_len -= len(cur_data)
if remain_len == 0:
break
return True, recv_data
class TestServer:
def __init__(self, ip_addr, port):
self.ip_addr = ip_addr
self.port = port
self.sock = socket.socket()
def start(self):
self.sock.bind((self.ip_addr, self.port))
self.sock.listen(5)
print('server listen on {}:{}'.format(self.ip_addr, self.port))
client_sock, client_info = self.sock.accept()
print('recv connection from {}'.format(client_info))
# 因为是测试程序 所以就直接接收发送时数据的长度
msg_len = 31
recv_suc, msg_data = recv_some(client_sock, msg_len)
if not recv_suc:
return
# 反序列话收到的数据为MsgPlayerLoginRequest并打印其中数据
recv_msg = MsgPlayerLoginRequest()
recv_msg.ParseFromString(msg_data)
print(f'recv MsgPlayerLoginRequest : playerName = {recv_msg.playerName}, playerPass = {recv_msg.playerPass}, ip = {recv_msg.ip}, id = {recv_msg.id}')
if __name__ == '__main__':
one_server = TestServer('127.0.0.1', 10088)
one_server.start()
运行程序,客户端输出:
connect to 127.0.0.1:10088
connect OK
send MsgPlayerLoginRequest ok
服务端输出:
server listen on 127.0.0.1:10088
recv connection from ('127.0.0.1', 49723)
recv MsgPlayerLoginRequest: playerName = hello hi, playerPass = 123456, ip = 127.0.0.1, id = 1