.proto:文件中的数据组织结构是以messgae打头的,message消息体里面又可以嵌套message,总之,初次接触有点生疏不知所措,但是多写写多测测,还是很容易搞定这种proto文件格式的,搞定后就是应用在我们的项目中了。比如,我们后台使用的是Java框架的一整套东西,接口都是以Restful Api的形式发布出去的,如果单纯的用json作为各个平台、语言之间的数据交换格式的话,C++那边应该会略显吃力,C++对Json的解析略显费劲。Python是很OK的。
话又说回来,既然我们学会了使用protobuf,知道其是很nice的,而且跨平台,不区分语言,和什么C#、C++、Ruby、JS、Java、Python等完美结合,而且这种二进制的字节传输,在网络带宽上也是节约的不少,总之很棒,值得一试。
本篇,我将结合Python和Java这两个热门语言展开一次protocol buffer的面对面“交流”的应用实例讲解,发起点来自于Python端,其使用requests模块中的post方法利用HTTP协议将二进制文件流传入到Java端,作为接收端,Java提供Python发起Post请求所需要的Restful Api接口,说白了就是url地址。
Come on,跟上老司机的飙车节奏,我们造起来!
一、Java和Python的通用协议文件 == Student.proto
(1)上一篇中,我们已经在Java应用中构建了我们的message【proto数据交换格式或协议文件】,如下
如果一脸懵逼的话,请回到上一篇:Spring-Boot -- Google Protocol Buffer的应用
(2)贴出来,大家欣赏一下,待会这个协议文件要“借给”Python用一用
Student.proto
syntax = "proto3";//这个版本的protoc的protobuf编译器已经可以支持proto2语法和proto3的语法
package com.appleyk.protocol.model;
option java_outer_classname = "SObjectModel"; //输出的类名
message Students{
repeated Student students = 1;
message Student{
int64 id = 1 ;
string name = 2 ;
repeated string hobbies = 3 ;
map<string,double> grades = 4 ;
}
}
二、Student.proto文件在Python中的应用
(1)上上篇,我讲了 protobuf在Python中的应用【Windows环境下】,一脸懵逼的请点开链接返回去重新复习
(2)第一条没看见或忽视或懒得看的,我热心肠再走一遍流程吧,
首先:将proto协议文件放在一个python工作空间【文件夹】下,如:
记得上面说的,这个文件是从Java环境编译过来的,有些东西是要“还”给Java的,去掉他们如下:
其次:protoc编译命令走一遍,来来来,跟着我一起在CMD命令窗口中敲
D:\proto\Test>protoc -I=D:/proto/Test --python_out=D:/proto/Test D:/proto/Test/Student.proto
然后,你会看到:
如果不点开看看它,我还真做不到,So,揭开Student_pb2.py的面纱吧:
1.
2.
3. 剩下的就是嵌套消息体Student对象的字段描述了
4. 接着,同目录下新建一个py模块,引用上面的Student_pb2.py
下面直接贴出demo :
#!/usr/bin/env Python3
# -*- encoding:utf-8 *-*
# Author :
import Student_pb2
#Students消息体的实例对象
students = Student_pb2.Students()
#由于Students消息体的实例对象students中包含repeated复合类型的字段students
#因此,这里我们需要用字段students的add方法创建一个Student类型的对象
student = students.students.add()
#有了对象,我们开始赋值【实例化对象的字段属性】
student.id = 1001
student.name = "Appleyk"
#取出student对象的hobbies兴趣爱好字段
#其虽然也是repeated修饰,但类型却不是一个复合类型,而是一个List[]
#因此,其具有append方法
hobbies = student.hobbies
hobbies.append("打游戏")
hobbies.append("Watch NBA")
hobbies.append("睡觉")
#取出成绩字典数据
grades = student.grades
grades["语文"] = 95.6
grades["数学"] = 85.5
#最后打印
print(students)
运行,IO窗口输出信息如下:
我们可以使用SerializeToString()方法对对象进行序列化成字符串如下:
如果我们把其写进一个文件,会怎么样呢?
#写入文件
data = students.SerializeToString()
with open("c:/data.pbf","wb") as fwriter:
try:
fwriter.write(data)
print("data二进制字节数据写入文件成功!")
except Exception as ex:
print(ex)
运行效果如下:
文件如下【由于是二进制文件,我就随便选了一种格式进行打开,当然,一堆乱码就对了】:
关于Protobuf在python中的应用,上面已经讲的很细了,message消息体定义也很灵活,python读写proto文件编译后的模块中的对象也是很随意,总之比Java中要优秀的多,这也是很多人喜欢Python的原因之一吧。毕竟作为一名一天不写代码就坐立不安的程序员来说,能写代码是一件很棒的事情,但是在功能一样的前提下少写代码那就更是一件很伟大的事了。
“博主,你说的Protobuf在Http协议中传输的部分在哪呢? Are you Kiding ?”
还是那句话,步子大了容易扯到旦,没有上面的循序渐进,不可能有下面的完美输出,好了,继续
三、Java后端发布Restful风格的API接口
(1)基于上一篇源码继续往下写,不知道源码在哪下载的,请点开链接自行下载
Controller代码如下:
package com.appleyk.protocol.controller;
import java.util.List;
import java.util.Map;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import com.appleyk.protocol.model.SObjectModel.Students;
import com.appleyk.protocol.model.SObjectModel.Students.Student;
import com.appleyk.protocol.result.ResponseMessage;
import com.appleyk.protocol.result.ResponseResult;
@RestController
@RequestMapping("/appleyk/student")
public class StudentController {
@PostMapping("/save")
public ResponseResult SaveStudent(MultipartFile data) throws Exception{
byte[] object = data.getBytes();
Students students = Students.newBuilder().build().parseFrom(object);
List<Student> studentsList = students.getStudentsList();
for (Student student : studentsList) {
System.out.println("in: "+student.getId());
System.out.println("name: "+student.getName());
//取兴趣爱好
for(int i =0;i<student.getHobbiesCount();i++){
System.out.println("兴趣爱好:"+student.getHobbies(i).toString());
}
//取各科成绩
Map<String, Double> gradesMap = student.getGradesMap();
System.out.println("各科成绩:"+gradesMap.toString());
System.out.println("name: "+student.getName());
System.out.println("========================");
}
return new ResponseResult(ResponseMessage.OK);
}
}
注意:传二进制流,统一用文件的形式,也就是,二进制数据写入文件,通过文件进行Post
工具测试Controller【工具下载链接:Insomnia(API测试工具)】:
接口没毛病,我们直奔后台看输出:
完美匹配Python端的数据:
四、Python端通过Post,传二进制文件给Java端
实现protobuf格式的数据通信很简单,难就难在协议文件proto的如何定义,以及数据如何构造上,有了这两个,即可实现跨平台、跨语言的基于Http协议的数据传输,比如,在Python中,我们可以引入requests模块,实现post
#!/usr/bin/env Python3
# -*- encoding:utf-8 *-*
import requests
import Student_pb2
import os
students = Student_pb2.Students(); #声明一个Students的对象
#复合对象repeated【student】,添加一个
s1 = students.students.add();
s1.id = 1001
s1.name = "张三"
hobbies = s1.hobbies
hobbies.append("睡觉")
hobbies.append("吃饭")
hobbies.append("睡觉")
grades = s1.grades
grades["语文"] = 98.5
grades["数学"] = 85.5
grades["英语"] = 95.0
#复合对象repeated【student】,添加一个
s2 = students.students.add();
s2.id = 1002
s2.name = "李玟"
hobbies = s2.hobbies
hobbies.append("逛街")
hobbies.append("化妆")
grades = s2.grades
grades["语文"] = 88.5
grades["数学"] = 75.5
grades["英语"] = 92.0
url = "http://localhost:8080/appleyk/student/save"
data = students.SerializeToString()
path = 'C:/data.pbf'
with open(path, 'wb')as fp:
fp.write(data) #以二进制的方式写入文件
file = open(path, 'rb')
files = {'data': file}
try:
r=requests.post(url,files=files) #post 文件
code = r.status_code
if(code == 200):
print(r.text)
file.close() #别忘关闭资源
if(os.path.exists(path)):
#如果存在,删除文件
os.remove(path)
except Exception as ex:
print(ex)
Python端运行效果如下:
出现200,即说明Python和Java之间的Protobuf通信已经跑通了,不信,我们去Java端看看控制台输出的信息
五、为什么要用protocol buffer?
幽默风趣的说就是:
如果给你一个作业,假如用Java写要用100行代码,而用Python写只需要用50行代码,只为了单纯的快速的完成作业而言,你们会选哪个? 让我选,我肯定选Python,因为我觉得他很帅,nice !
如果给你一个平台,接口都提供好了,不管他是Php的、Java的还是.Net的,我告诉你,现在你需要接入进来,我不管你用什么语言,用Ruby、Js、还是C#亦或者是C++,总之,你可以用到的交换数据格式有Json的、有Xml的、还有Protobuf的,而我告诉你,同样信息量的数据,Xml和Json的体积要明显高于Probuf的数据格式,比如,你接入一个查询接口,该接口返回一个包含100条对象的Json数据,它的大小假如是1M,响应时间假如是0.5秒,而我告诉你,如果你选择用Protobuf的数据交换格式,同样查询100条对象的数据,pb的大小可能是200多K,响应时间可能是0.017秒,没错,性能就是这么优越,你会怎么选? 如果你觉得无所谓,还是Json用着舒服多了,那就等系统到时候出现性能瓶颈再说吧,如果你选择了protobuf,那么在数据的存储和访问上,你接入第三方接口的响应时间不仅会大大降低同时还会减少访问接口所带来的网络带宽的消耗【同比Json和Xml而言】
最后再次附上Java端的项目地址: Spring-Boot-Protocol-Buffer