基于HTTP协议通过protobuf进行的前后端交互方案 转

转自:https://zhuanlan.zhihu.com/p/128095667

什么是protobuf

官方介绍如下:

GoogleProtocolBuffers简称Protobuf,它提供了一种灵活、高效、自动序列化结构数据的机制,可以联想XML,但是比XML更小、更快、更简单。仅需要自定义一次你所需的数据格式,然后用户就可以使用Protobuf编译器自动生成各种语言的源码,方便的读写用户自定义的格式化的数据。与语言无关,与平台无关,还可以在不破坏原数据格式的基础上,依据老的数据格式,更新现有的数据格式。总的来说就是以下几个特点

  • 追求更小,更快,更简洁。
  • 与平台无关,与语言无关。
  • 前后端需要事先定义数据格式,这点相比较json确实是颇为棘手。

既然前后端文件格式比较棘手,那接下来就看下protobuf前后端的数据格式是怎么样的。

protobuf的数据格式

前后端定义同名的.proto后缀的文件,比如test.proto以此文件来当文档来进行沟通。省去了前后端开发人员撕逼甩锅的时间。
比如以下为test.proto文件格式:

enum FOO {
  BAR = 1;
}

message Test {
  required float num  = 1;
  required string payload = 2;
  optional string payloads = 3;
}

message AnotherOne {
  repeated FOO list = 1;
}

首先看下第一部分,enum这个词就是枚举,类似于java语言中的enum,作用也是一样。第二部分表示的是一个消息体,消息体的名字是Test,类似于js中的对象,里面有num,payload,payloads三个属性。required,optional,repeated是修饰符。required表明该字段只能也且必须出现1次。optional字段表示该字段可以出现0次或者1次。repeated,从字面意思看有“重复”的意思,实际上它就是用来指定某一个字段可以存放同一个类型的多个数据(当然也可以是0个或者1个)。第三部分是另一个消息体,名字叫做AnotherOne。

介绍了protobuf的数据格式,我们就需要使用protobuf进行前后端交互了,但是在进行前后端交互之前,需要安装一下前后端的protobuf的开发环境。

protobuf的前后端环境

由于protobuf交互的特殊性,为了节约前后端交互的资源,就必须牺牲下开发时间,来做前后端交互之前的准备工作。

前端需要安装解析protobuf的环境。mac推荐使用brew安装protobuf环境。此操作需要先安装Homebrew环境。具体的Homebrew的安装自行搜索。windows的前端环境安装有点不一样,自行搜索。

brew install protobuf  // 安装protobuf环境
protoc --version // 测试环境是否安装

后端以node.js为例子:安装bufferhelper以及protocol-buffers进行解析protobuf文件

npm install bufferhelper
npm install protocol-buffers

在进行前后端交互之前,前后端需要进行编译proto文件。test.proto为前端以及后端相同的proto文件。因为前后端都是js编写,所以需要编译先为js文件再执行。首先进入node项目的proto的目录下面,执行下面编译的命令之后会生成test_pb.js文件。最后js只需要引入以及解析这个文件即可。前端也需要执行这样的操作,因为我这边是前后端分离的。是两个项目,所以两个项目都需要编译。

前后端都进入proto同级文件下执行以下操作:

protoc --js_out=import_style=commonjs,binary:. test.proto

protobuf的前后端交互

protobuf文件和环境都准备好之后,就可以进行交互了。交互也分为前端给后端传数据和后端给前端传数据。下面两个例子分别为get和post方法请求后端数据。

  1. 前端给后端传数据,post请求

前端需要引入需要依赖的文件,即为之前编译之后的文件。然后需要进行proto对象的赋值,然后转换为二进制的形式给后端 。代码如下:

 import awesome from '../../proto/test_pb.js'
 import protobuf from 'protobufjs'
 
 
    let message = new awesome.Test() // 实例化
    // 赋值
    message.setNum(23)
    message.setPayload('asd')
    // 序列化
    let bytes = message.serializeBinary() //  字节流
    let blob = new Blob([bytes], {type: 'buffer'});
    axios({
      method:'post',
      url: '/proto/send',
      data: blob,
      headers: {
        'Content-Type': 'application/octet-stream' // 这里根据后台要求进行设置的,如果没有要求应该是 application/octet-stream (二进制流)
      }
    }).then(res => {
      console.log(res)
    }).catch((error) => {
      console.log(error)
    })

后端需要也需要引入编译之后的文件。并解析前端发送来的字节流。

let BufferHelper = require('bufferhelper');

app.post('/proto/send', function (req, res) {
  let bufferHelper = new BufferHelper();
  req.on("data", function (chunk) {
    bufferHelper.concat(chunk);
  });
  req.on('end', function () {
    let protobuf = require('protocol-buffers')
    let buffer = bufferHelper.toBuffer();
    console.log(buffer) // 这里已经就是二进制文件了
    let message3 = awesome.Test.deserializeBinary(buffer)
    console.log(message3.getNum()) // 打印的就是前端传的23
    let pm = awesome.Test.deserializeBinary(buffer)
    let protoBuf = pm.toObject()
    console.log(protoBuf) // 打印的是{ num: 23, payload: 'asd', payloads: 'asds' }
    console.log(protoBuf.num) // 打印23
  });
})

2.后端给前端传数据,get请求

前端用axios请求/proto/get的路由,在回调函数里的res.data为后端的返回值。进行以下操作。打印出来的message3也是解析好的文件。

axios({
      method:'get',
      url: '/proto/get',
      headers: { 'contentType':'application/x-protobuf'} }).then(res => {
      let message3 = awesome.Test.deserializeBinary(res.data.data)
      let nums = message3.getNum()
      console.log(nums) // nums=42。解析出来就是后端传过来的42
      let pm = awesome.Test.deserializeBinary(res.data.data)
      let protoBuf = pm.toObject()
      console.log('protoBuf: ', protoBuf) // 打印出来是一个对象
    }).catch((error) => {
      console.log(error)
    })

后端给messages.Test进行赋值,并转化protobuf为二进制给前端。这里需要注意的是要先JSON.stringify之后才可以给前端。不然的话前端会转化成乱码的。

app.get('/proto/get', function (req, res) {
  let protobuf = require('protocol-buffers')
  let messages=protobuf(fs.readFileSync('./proto/test.proto')
  let buf = messages.Test.encode({
    num: 42,
    payload: 'hello world node js and javahhh呵呵呵',
    payloads: ''
  })
  console.log(buf) // 打印出来的是二进制流
  res.send(JSON.stringify(buf)); //需要进行json化然后给前端。不然的话浏览器会自动解析成文字的
})

最后:

使用protobuf虽然会比较麻烦,但是也会有以下几点好处:

  • 传输效率高,性能好。
  • 支持多种编程语言,在Google官方发布的源代码中包含了c++、java、Python三种语言。
  • 产品经理改需求的时候会比较麻烦从而放弃修改需求。因为需要前后端都改protobuf文件并同步前后端。

以上就是小编关于protobuf的认知,如有错误请及时留言待我加以改正。

点赞

发表回复

电子邮件地址不会被公开。必填项已用 * 标注