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函数 | 例子 | 反序列化解析后形式 | 例子 |
---|---|---|---|---|---|
全小写 | taskid | set+大写开头 | setTaskid | 全小写 | taskid |
大写开头 | Taskid | set+大写开头 | setTaskid | 全小写 | taskid |
小驼峰 | taskId | set+大写开头 | setTaskid | 全小写 | taskid |
下划线 | task_id | set+大驼峰 | setTaskId | 小驼峰 | taskId |
连续下划线 | task_i_d | set+大驼峰 | 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文件,配合一些封装好的函数使用(还有待测试验证)。