Protocol Buffer学习笔记(Java&NodeJS)

字数 1034阅读 1645

什么是Protocol Buffer

Protocol Buffers(也称protobuf)是Google公司出品的一种独立于开发语言,独立于平台的可扩展的结构化数据序列机制。通俗点来讲它跟xml和json是一类。是一种数据交互格式协议。
主要优点是它是基于二进制的,所以比起结构化的xml协议来说,它的体积很少,数据在传输过程中会更快。另外它也支持c++、java、python、php、javascript等主流开发语言。

官网地址:https://developers.google.com/protocol-buffers/

Proto3安装

下载地址:3.x.x的版本基本都按照操作系统和语言进行了区分,系统包里只包含了protoc命令,语言包则是用于编译后使用,比如java需要生成对应的jar包。这里可以根据需要下载对应的操作系统和语言包,比如这里我下载的是protoc-3.5.1-osx-x86_64.zip(苹果系统)和protobuf-java-3.5.1.tar.gz(java语言)。

  • unzip protoc-3.5.1-osx-x86_64.zip
  • 在/etc/profile中添加环境变量PROTOCTL_BUFFER_HOME(protoc-3.5.1-osx-x86_64.zip解压后目录),并在PATH中添加$PROTOCTL_BUFFER_HOME/bin
  • 查看版本:protoc --version :输出 libprotoc 3.5.1

以下部分只为自行编译生成对应的jar包,实际上maven中央仓库中已经存在了

  • tar -zxcf protobuf-java-3.5.1.tar.gz,解压后目录名称为protobuf-3.5.1
  • cd protobuf-3.5.1/src,创建软连接 ln -s $PROTOCTL_BUFFER_HOME/bin/protoc protoc
  • cd protobuf-3.5.1/javamvn package(maven请自行安装),成功后会在protobuf-3.5.1/java/code/target下生成protobuf-java-3.5.1.jar
  • 然后将protobuf-java-3.5.1.jar上传到maven私服或者安装到本地仓库就可以使用了
mvn install:install-file -Dfile=protobuf-java-3.5.1.jar -DgroupId=com.google.protobuf -DartifactId=protobuf-java -Dversion=3.5.1 -Dpackaging=jar
  • pom中添加依赖
<!-- protocol buffer -->
<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>3.5.1</version>
</dependency>

Proto2安装

下载地址:这里只是操作系统包,比如这里我下载的是protoc-2.6.1-osx-x86_64.exe,语言包protobuf-2.6.1.tar.gz

  • mv protoc-2.6.1-osx-x86_64.exe protoc
  • 将上面重命名后的protoc文件所在目录加到系统环境变量PATH中
  • 查看版本:protoc --version :输出 libprotoc 2.6.1

以下部分只为自行编译生成对应的jar包,实际上maven中央仓库中已经存在了

  • tar -zxcf protobuf-2.6.1.tar.gz,解压后目录名称为protobuf-2.6.1
  • cd protobuf-2.6.1/src,创建软连接 ln -s $PROTOCTL_BUFFER_HOME/bin/protoc protoc
  • cd protobuf-2.6.1/javamvn package(maven请自行安装),成功后会在protobuf-2.6.1/java/target下生成protobuf-java-2.6.1.jar
  • 然后将protobuf-java-2.6.1.jar上传到maven私服或者安装到本地仓库就可以使用了
mvn install:install-file -Dfile=protobuf-java-2.6.1.jar -DgroupId=com.google.protobuf -DartifactId=protobuf-java -Dversion=2.6.1 -Dpackaging=jar
  • pom中添加依赖
<!-- protocol buffer -->
<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>2.6.1</version>
</dependency>

Proto使用

  • 先编写proto文件,具体语法请参考通信协议之Protocol buffer(Java篇)
  • 生成java文件:protoc --java_out=. XXXX.proto
  • 生成js文件:protoc --js_out=import_style=commonjs,binary:. XXXX.proto 『只有proto3支持该命令』
  • proto2与proto3语法上有一些不同,但是在使用时却没有特别的不同之处,此外proto3向下兼容proto2,所以可以只安装proto3,然后通过在proto文件中声明『syntax = "proto2";或者syntax = "proto3";』来指定类型

proto例子

//syntax = "proto2";
package com.data.upload.proto;

// 4.1 网约车平台公司基本信息接口
message BaseInfoCompany
{
    // 公司标识
    required string CompanyId       = 1;

    // 公司名称
    required string CompanyName     = 2;

    // 统一社会信用代码
    required string Identifier      = 3;

    // 注册地行政区划代码
    required uint32 Address         = 4;

    // 经营范围
    required string BusinessScope   = 5;

    // 通讯地址
    required string ContactAddress  = 6;

    // 经营业户经济类型
    required string EconomicType    = 7;

    // 注册资本
    required string RegCapital      = 8;

    // 法人代表姓名
    required string LegalName       = 9;

    // 法人代表身份证号
    required string LegalID         = 10;

    // 法人代表电话
    required string LegalPhone      = 11;

    // 法人代表身份证扫描件文件编号
    optional string LegalPhoto      = 12;

    // 状态
    required uint32 State           = 13;

    // 操作标识
    required uint32 Flag            = 14;

    // 更新时间
    required uint64 UpdateTime      = 15;

    // 保留字段
    optional string Reserved        = 16;
}

// 4.2 网约车平台公司营运规模信息信息接口
message BaseInfoCompanyStat
{
    // 公司标识
    required string CompanyId       = 1;

    // 平台注册网约车辆数
    required uint32 VehicleNum      = 2;

    // 平台注册驾驶员数
    required uint32 DriverNum       = 3;

    // 操作标识
    required uint32 Flag            = 4;

    // 更新时间
    required uint64 UpdateTime      = 5;

    // 保留字段
    optional string Reserved        = 6;
}

enum IpcType
{
    // 4.1 网约车平台公司基本信息接口
    baseInfoCompany                             = 0x1001;

    // 4.2 网约车平台公司营运规模信息信息接口
    baseInfoCompanyStat                         = 0x1002;
}

message OTIpc
{
     // 公司标识
     required string CompanyId                                  = 1;

     // 消息来源标识
     required string Source                                     = 2;

     // 业务接口代码
     required IpcType IPCType                                   = 3;

    // 4.1 网约车平台公司基本信息接口
    repeated BaseInfoCompany baseInfoCompany                    = 0x1001;

    // 4.2 网约车平台公司营运规模信息信息接口
    repeated BaseInfoCompanyStat baseInfoCompanyStat            = 0x1002;
}

message OTIpcList
{
     repeated OTIpc otpic                     = 1;
}  

java中使用Protocol Buffer

  • 添加依赖
<!-- protocol buffer -->
<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>2.6.1</version>
</dependency>
  • Client端
  //创建对象
  OTIpcDef.BaseInfoCompany.Builder baseInfoCompanyBuilder = OTIpcDef.BaseInfoCompany.newBuilder();
  baseInfoCompanyBuilder.setAddress(110011);
  baseInfoCompanyBuilder.setCompanyId("companyId");
  baseInfoCompanyBuilder.setCompanyName("companyName");
  baseInfoCompanyBuilder.setIdentifier("identifier");
  baseInfoCompanyBuilder.setBusinessScope("BusinessScope");
  baseInfoCompanyBuilder.setContactAddress("ContactAddress");
  baseInfoCompanyBuilder.setEconomicType("EconomicType");
  baseInfoCompanyBuilder.setRegCapital("RegCapital");
  baseInfoCompanyBuilder.setLegalName("LegalName");
  baseInfoCompanyBuilder.setLegalID("LegalID");
  baseInfoCompanyBuilder.setLegalPhone("LegalPhone");
  baseInfoCompanyBuilder.setState(0);
  baseInfoCompanyBuilder.setFlag(1);
  baseInfoCompanyBuilder.setUpdateTime(20180226121212l);

  OTIpcDef.BaseInfoCompany baseInfoCompany = baseInfoCompanyBuilder.build();

  OTIpcDef.OTIpc.Builder otIpcBuilder = OTIpcDef.OTIpc.newBuilder();
  otIpcBuilder.setCompanyId("companyId");
  otIpcBuilder.setSource("Source");
  otIpcBuilder.setIPCType(OTIpcDef.IpcType.baseInfoCompany);

  //如果一次传递多条记录可以使用list
  //List<OTIpcDef.BaseInfoCompany> list  = new ArrayList<OTIpcDef.BaseInfoCompany>();
  //list.add(baseInfoCompany);
  //otIpcBuilder.addAllBaseInfoCompany(list);

  //也可以用add方法一个一个的添加
  otIpcBuilder.addBaseInfoCompany(baseInfoCompany);
  otIpcBuilder.addBaseInfoCompany(baseInfoCompany);

  OTIpcDef.OTIpc otIpc = otIpcBuilder.build();

  OTIpcDef.BaseInfoCompanyStat.Builder baseInfoCompanyStatBuilder = OTIpcDef.BaseInfoCompanyStat.newBuilder();
  baseInfoCompanyStatBuilder.setCompanyId("companyId");
  baseInfoCompanyStatBuilder.setDriverNum(10);
  baseInfoCompanyStatBuilder.setFlag(0);
  baseInfoCompanyStatBuilder.setUpdateTime(20180226121212l);
  baseInfoCompanyStatBuilder.setVehicleNum(5);

  OTIpcDef.BaseInfoCompanyStat baseInfoCompanyStat = baseInfoCompanyStatBuilder.build();

  OTIpcDef.OTIpc.Builder otIpcBuilder2 = OTIpcDef.OTIpc.newBuilder();
  otIpcBuilder2.setCompanyId("companyId");
  otIpcBuilder2.setSource("Source");
  otIpcBuilder2.setIPCType(OTIpcDef.IpcType.baseInfoCompanyStat);

  otIpcBuilder2.addBaseInfoCompanyStat(baseInfoCompanyStat);

  OTIpcDef.OTIpc otIpc2 = otIpcBuilder2.build();

  OTIpcDef.OTIpcList.Builder oTIpcListBuilder = OTIpcDef.OTIpcList.newBuilder();
  oTIpcListBuilder.addOtpic(otIpc);
  oTIpcListBuilder.addOtpic(otIpc2);

  OTIpcDef.OTIpcList otIpcList = oTIpcListBuilder.build();
  //序列话数据
  byte[] array = otIpcList.toByteArray();

  HttpClientUtils httpClientUtils = new HttpClientUtils();
  httpClientUtils.doPost4ProtocleBuffer("http://localhost:3000/demo/protoc",array);

HttpClientUtils.java

import org.apache.http.client.config.RequestConfig;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.ByteArrayEntity;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.apache.http.impl.client.LaxRedirectStrategy;
import org.apache.http.impl.conn.PoolingHttpClientConnectionManager;
import org.apache.http.util.EntityUtils;

public class HttpClientUtils {

    private CloseableHttpClient httpClient;

    private RequestConfig requestConfig;

    public HttpClientUtils(){
        init();
    }


    public void init(){

        PoolingHttpClientConnectionManager httpClientConnectionManager = new PoolingHttpClientConnectionManager();
        // 创建全局的requestConfig
        this.requestConfig = RequestConfig.custom().build();
        // 声明重定向策略对象
        LaxRedirectStrategy redirectStrategy = new LaxRedirectStrategy();

        this.httpClient = HttpClients.custom().setConnectionManager(httpClientConnectionManager)
                .setDefaultRequestConfig(requestConfig)
                .setRedirectStrategy(redirectStrategy)
                .build();
    }

    public void doPost4ProtocleBuffer(String url, byte[] bytes) throws Exception {


        // 创建http POST请求
        HttpPost httpPost = new HttpPost(url);
        httpPost.setConfig(this.requestConfig);
        httpPost.setHeader("Connection", "keep-alive");
        httpPost.setHeader("Content-type", "application/x-protobuf");
        httpPost.setHeader("Accept-Encoding", "gzip");
        httpPost.setHeader("Accept-Charset", "utf-8");

        if (bytes != null) {
            // 构造一个请求实体
            ByteArrayEntity byteArrayEntity = new ByteArrayEntity(bytes);
            byteArrayEntity.setContentType("application/x-protobuf");
            // 将请求实体设置到httpPost对象中
            httpPost.setEntity(byteArrayEntity);
        }
        CloseableHttpResponse response = null;
        try {
            // 执行请求
            response = this.httpClient.execute(httpPost);
           
        } finally {
            if (response != null) {
                response.close();
            }
        }
    }
}
  • server端
InputStream in = request.getInputStream();
OTIpcDef.OTIpcList otIpcList = OTIpcDef.OTIpcList.parseFrom(in);
List<OTIpcDef.OTIpc> list= otIpcList.getOtpicList();
for(OTIpcDef.OTIpc otIpc : list){
    String companyid = otIpc.getCompanyId();
    String source = otIpc.getSource();
    OTIpcDef.IpcType ipcType = otIpc.getIPCType();
    if(ipcType == OTIpcDef.IpcType.baseInfoCompany){
        List<OTIpcDef.BaseInfoCompany> baseInfoCompanyList = otIpc.getBaseInfoCompanyList();
        for(OTIpcDef.BaseInfoCompany baseInfoCompany : baseInfoCompanyList){
            String companyName = baseInfoCompany.getCompanyName();
        }
    }else if(ipcType == OTIpcDef.IpcType.baseInfoCompanyStat){
        List<OTIpcDef.BaseInfoCompanyStat> baseInfoCompanyStatList = otIpc.getBaseInfoCompanyStatList();
        for(OTIpcDef.BaseInfoCompanyStat baseInfoCompanyStat : baseInfoCompanyStatList){
            int driverNum = baseInfoCompanyStat.getDriverNum();
        }
    }
}

nodejs中使用Protocol Buffer

  • 安装依赖
    npm install google-protobuf --save
    npm install bufferhelper --save

  • Client端

var OTIpcDefProto = require('../protocbuf/OTIpcDef_pb');
var http = require('http');

//业务对象封装
var baseInfoCompany = new OTIpcDefProto.BaseInfoCompany();
baseInfoCompany.setAddress(110011);
baseInfoCompany.setCompanyid("companyId");
baseInfoCompany.setIdentifier("identifier");
baseInfoCompany.setCompanyname("companyName公司名称");
baseInfoCompany.setBusinessscope("BusinessScope");
baseInfoCompany.setContactaddress("ContactAddress");
baseInfoCompany.setEconomictype("EconomicType");
baseInfoCompany.setRegcapital("RegCapital");
baseInfoCompany.setLegalname("LegalName");
baseInfoCompany.setLegalid("LegalID");
baseInfoCompany.setLegalphone("LegalPhone");
baseInfoCompany.setState(0);
baseInfoCompany.setFlag(1);
baseInfoCompany.setUpdatetime(20180226121212);

//业务类型封装
var otIpc = new OTIpcDefProto.OTIpc();
otIpc.setCompanyid("companyId");
otIpc.setSource("Source");
otIpc.setIpctype(OTIpcDefProto.IpcType.BASEINFOCOMPANY);
//可以多次调用add方法添加多条业务对象数据
otIpc.addBaseinfocompany(baseInfoCompany);

//统一封装为list传输
var otIpcList = new OTIpcDefProto.OTIpcList();
//可以通过add方法条件多条业务类型数据
otIpcList.addOtpic(otIpc);

//序列化对象
var contents = otIpcList.serializeBinary();


var options = {
    host: 'localhost',
    port: 3000,
    path: '/demo2/protoc',
    method: 'POST',
    headers: {
        'Content-Type': 'application/x-protobuf'
    }
};

//发送请求
var req = http.request(options, function(res){
    // res.setEncoding('uft8');
    res.on('data', function(data){
        console.log(data);
    });
});

//转成buffer
var buffer = new Buffer(contents);
//只支持string和buffer类型
req.write(buffer);
req.end();
  • Server端(express)
var express = require('express');
var router = express.Router();
var OTIpcDefProto = require('../protocbuf/OTIpcDef_pb');
var BufferHelper = require('bufferhelper');


// http://localhost:3000/demo/
router.post('/protoc', function(req, res, next) {
    //数据接收,可以使用bufferHelper接收protocolbuffer数据
    var bufferHelper = new BufferHelper();
    req.on("data", function (chunk) {
        bufferHelper.concat(chunk);
    });
    req.on('end', function () {
        var buffer = bufferHelper.toBuffer();
        //buffer转换为proto对象
        var otIpcList = OTIpcDefProto.OTIpcList.deserializeBinary(new Uint8Array(buffer));

        for(var i=0;i<otIpcList.getOtpicList().length;i++) {
            console.log(i+"========================================");
            var otIpc = otIpcList.getOtpicList()[i];
            var companyid = otIpc.getCompanyid();
            var source = otIpc.getSource();
            var iPCType = otIpc.getIpctype();
            console.log(companyid);
            console.log(source);
            console.log(iPCType);
            if(iPCType == OTIpcDefProto.IpcType.BASEINFOCOMPANY){
                var baseInfoCompanyList = otIpc.getBaseinfocompanyList();
                for(var j=0;j<baseInfoCompanyList.length;j++){
                    console.log(j+"===============baseInfoCompanyList=================");
                    var baseInfoCompany = baseInfoCompanyList[j];
                    console.log(baseInfoCompany.toObject());
                    console.log(baseInfoCompany.getCompanyid());
                    console.log(baseInfoCompany.getCompanyname());
                }

            }else if(iPCType == OTIpcDefProto.IpcType.BASEINFOCOMPANYSTAT){
                var baseInfoCompanyStatList = otIpc.getBaseinfocompanystatList();
                for(var j=0;j<baseInfoCompanyStatList.length;j++){
                    console.log(j+"===============baseInfoCompanyStatList=================");
                    var baseInfoCompanyStat = baseInfoCompanyStatList[j];
                    console.log(baseInfoCompanyStat.toObject());
                    console.log(baseInfoCompanyStat.getCompanyid());
                    console.log(baseInfoCompanyStat.getDrivernum());
                }
            }

        }

        console.log(otIpcList.toObject());

        res.send(otIpcList.toObject());
    });

});

module.exports = router;

这里可以将protocolbuffer数据的接收过程封装到app.js中

//以下代码要在路由映射的最上方声明,以保证其先被执行
app.use('/*',function(req, res, next) {

  var contentType = req.get('Content-Type');
  //判断contentType,如果是protobuf类型则将数据封装到req.body中
  if(contentType=='application/x-protobuf') {
      var bufferHelper = new BufferHelper();
      req.on("data", function (chunk) {
          bufferHelper.concat(chunk);
      });
      req.on('end', function () {
          var buffer = bufferHelper.toBuffer();
          req.body = buffer;
          console.log(req.body);
          next();
      });
  }else{
      next();
  }

});

然后在路由js中只需要按照如下方式接收数据即可

var otIpcList = OTIpcDefProto.OTIpcList.deserializeBinary(new Uint8Array(req.body));
  • Server端(restify)
    restify中接收proto数据比较简单,因为proto数据已经被封装到req.body中了,所以使用方式类似于上面express的第二种方法
var otIpcList = OTIpcDefProto.OTIpcList.deserializeBinary(new Uint8Array(req.body));

JSON与Protobuf相互转换

JAVA

<!-- protocol buffer format -->
<dependency>
    <groupId>com.googlecode.protobuf-java-format</groupId>
    <artifactId>protobuf-java-format</artifactId>
    <version>1.4</version>
</dependency>
  • json to proto
com.googlecode.protobuf.format.JsonFormat jsonFormat = new JsonFormat();
com.google.protobuf.Message.Builder builder = OTIpcDef.BaseInfoCompany.newBuilder();
//这里实际上需要提供一个json字符串,这里假设这个json是从某个对象转换而来的
String json = com.alibaba.fastjson.JSON.toJSONString(myObject);
//该方法会将json中与builder所代表的对象中的属性做merge,也就是说只要字段名称和类型一致即可进行封装,对于字段名称和类型匹配不上的属性不予处理,方法成功后builder对象会完成属性值的封装。
jsonFormat.merge(new ByteArrayInputStream(json.getBytes()), builder);     
  • proto to json
OTIpcDef.OTIpcList otIpcList = oTIpcListBuilder.build();
//proto对象转json
com.googlecode.protobuf.format.JsonFormat jsonFormat = new JsonFormat();
String json =jsonFormat.printToString(otIpcList);

nodejs

  • json to proto
    编写json2Proto.js,里面就一个方法,用于将json字符串转换为封装好的proto对象
var json2proto = function (json_str,protoObject) {
    Array.prototype.contains = function ( needle ) {
        for (i in this) {
            if (this[i] == needle) return true;
        }
        return false;
    }
    var p_json_str = json_str;
    var p_json = eval("(" + p_json_str + ")");
    var p_json_key_array = [];
    var i = 0;
    for(var p in p_json){//遍历json对象的每个key/value对,p为key
        p_json_key_array[i] = p;
        i++;
    }
    var s_json = protoObject.toObject();
    for(var p in s_json){//遍历json对象的每个key/value对,p为key
        if (p_json_key_array.contains(p)) {
            var setMethod = "set"+p.charAt(0).toUpperCase() + p.slice(1);
            protoObject[setMethod](p_json[p]);
        }
    }
    return protoObject;
}
module.exports = json2proto;

调用方法

var OTIpcDefProto = require('../protocbuf/OTIpcDef_pb');
var json2proto = require('../json2Proto');
//json字符串
var p_json_str = "{ companyid: '公司ID'," +
    "companyname: 'companyId'," +
    "identifier : 'identifier'," +
    "address : 111111," +
    "businessscope : 'businessscope'," +
    "contactaddress : 'contactaddress'," +
    "economictype : 'economictype'," +
    "regcapital : 'regcapital'," +
    "legalname : 'legalname'," +
    "legalid : 'legalid'," +
    "legalphone : 'legalphone'," +
    "legalphoto : 'legalphoto'," +
    "state : 0," +
    "flag : 1," +
    "updatetime: 20180226121212}";
var baseInfoCompany = json2proto(p_json_str,new OTIpcDefProto.BaseInfoCompany());

console.log(baseInfoCompany.toObject());

推荐阅读更多精彩内容