protocol buffer——nodejs中的实践

本次采是使用google提供的windows版本的protoc工具,下载地址为https://github.com/protocolbuffers/protobuf/releases,选择适合自己的环境下载即可(可能还需要设置环境变量,按照提示信息进行安装即可。如果没有现成的工具,可选择编译源码)

一、编写.proto文件

本次所提供的.proto文件采用较为简单的格式,其他格式如引用、枚举等方式暂不举例:

syntax="proto3";      //不指定proto3,默认为proto2格式
package pbdata;      //包名,同一项目工程的文件可定义在一个包内

message RealTrackData{ 
        string type = 1;            //非枚举类型,首个默认值不能为0
        string Task_i_d = 2;
        string Time = 3;
        string Longitude = 4;
        string Latitude = 5;
        string Direction = 6;
        string Target_speed = 7;
}

注意proto文件中的命名方式,经过protol序列化与反序列化后,会对驼峰下划线进行转换,情况如下:

字段形式例子set函数例子反序列化解析后形式例子
全小写taskidset+大写开头setTaskid全小写taskid
大写开头Taskidset+大写开头setTaskid全小写taskid
小驼峰taskIdset+大写开头setTaskid全小写taskid
下划线task_idset+大驼峰setTaskId小驼峰taskId
连续下划线task_i_dset+大驼峰setTaskID小驼峰taskID

所以,建议字段名使用下划线方式进行定义(最后一种方式是一种不优雅的折中办法,不是很建议使用)

另外,枚举类型`enum`中首个属性的默认值必须为`0`,`enum`外的普通属性的默认值不能为`0`。

二、使用protoc.exe编译

按照以下方式执行(cmd和git bash皆可):

protoc.exe --js_out=import_style=commonjs,binary:. name.proto

commonjs是为了在nodejs中使用commonjs方式进行使用(注意commonjs与binary中间是英文逗号,),待编译文件.proto最好放在与proto.exe相同的目录,免得手动输入路径。编译过后会生成js文件(原文件名+pb.js),其中有些关键函数:

2.1 类定义

/**
 * Generated by JsPbCodeGenerator.
 * @param {Array=} opt_data Optional initial data array, typically from a
 * server response, or constructed directly in Javascript. The array is used
 * in place and becomes part of the constructed object. It is not cloned.
 * If no data is provided, the constructed object will be empty, but still
 * valid.
 * @extends {jspb.Message}
 * @constructor
 */
proto.pbdata.RealTrackData = function(opt_data) {
  jspb.Message.initialize(this, opt_data, 0, -1, null, null);
};
goog.inherits(proto.pbdata.RealTrackData, jspb.Message);
if (goog.DEBUG && !COMPILED) {
  /**
   * @public
   * @override
   */
  proto.pbdata.RealTrackData.displayName = 'proto.pbdata.RealTrackData';
}

2.2 set函数(以taskID为例)

/**
 * @param {string} value
 * @return {!proto.pbdata.RealTrackData} returns this
 */
proto.pbdata.RealTrackData.prototype.setTaskID = function(value) {
  return jspb.Message.setProto3StringField(this, 2, value);
};

2.3 get函数(以taskID为例)

/**
 * optional string Task_i_d = 2;
 * @return {string}
 */
proto.pbdata.RealTrackData.prototype.getTaskID = function() {
  return /** @type {string} */ (jspb.Message.getFieldWithDefault(this, 2, ""));
};

2.4 SerializeBinary 序列化函数

/**
 * Serializes the message to binary data (in protobuf wire format).
 * @return {!Uint8Array}
 */
proto.pbdata.RealTrackData.prototype.serializeBinary = function() {
  var writer = new jspb.BinaryWriter();
  proto.pbdata.RealTrackData.serializeBinaryToWriter(this, writer);
  return writer.getResultBuffer();
};

2.5 DeserializeBinary 反序列化函数

/**
 * Deserializes binary data (in protobuf wire format).
 * @param {jspb.ByteSource} bytes The bytes to deserialize.
 * @return {!proto.pbdata.RealTrackData}
 */
proto.pbdata.RealTrackData.deserializeBinary = function(bytes) {
  var reader = new jspb.BinaryReader(bytes);
  var msg = new proto.pbdata.RealTrackData;
  return proto.pbdata.RealTrackData.deserializeBinaryFromReader(msg, reader);
};

2.6 exports暴露方式

goog.object.extend(exports, proto.pbdata);

注意序列化函数SerializeBinary是原型函数,需要new对象后进行调用,反序列化DeserializeBinary是静态函数,直接通过类名进行调用。

一般的是先将数据经过set函数进行设值,再使用序列化函数转换为Unit8Array的二进制数据,通过一定的网络通讯(ws || HTTP),将二进制数据发送出去。接收端收到后,再通过反序列化得到实际的数据。

三、测试ws方式进行解析

前后端采用websocket进行通讯,为了方便测试,直接在ws建立连接时,就发送二进制数据。

该数据的通过上述的插件中set函数设置(有点像设置类中变量),例如:

let realtrack = new RealTrackData_pb();
realtrack.setTaskID('2');
//set其他值
let buf = realtrack.serializeBinary();

ws.send(buf);//伪代码,ws发送

此处,要注意set函数的参数类型要与proto中的类型一致,否则会设置无效。例如taskID定义为string,但set时传入的是数字0,则后面解析出来的就是无效的空字符串。切记勿照搬JS中类型灵活的特点

四、编写简单前端页面

前端采用H5的webSocket对象建立长连接,这里注意要设置以下接收数据的方式(只需要在client端设置):

ws.binaryType = 'arraybuffer'

如果不设置为arraybuffer,得到的二进制数据为默认的blob类型(还不知blob数据应该怎么使用)。

因为前端需要对接收到的二进制数据进行反序列化,所以要将proto工具转换的nodejs插件转化成浏览器可用的js文件,再在html上引用(vue这种可以直接用import引用的不需要转换)。这里采用browserify(需要npm 全局安装)将commonjs形式的模块转换成浏览器可使用的模块:(函数名和功能不变)

browserify RealTrackData_pb.js > realtrack.js

生成的js文件,可直接在html上通过script引用,在ws监听message中进行deserializeBinary反序列化操作:

ws.onmessage = function(evt) {
    var buf = new Uint8Array(evt);
    var data = proto.pbdata.RealTrackData.deserializeBinary(buf);
    console.log(data.toObject());
}

会发现打印结果与服务端通过set函数设置的值一致。

google-protocol与protocol.js的区别:
google-protocol生成相应语言的插件进行使用,protocol.js通过直接load需要使用的proto文件,配合一些封装好的函数使用(还有待测试验证)。