近期在做一个需求,该需求需要和后端进行交互,为了并行开发,就跟后端产生了如下的对话:
前端:老铁,可以给份mock数据吗?
后端:mock数据太麻烦了,你自己来吧!!!
前端:我怎么知道数据长啥样,如何mock呀!(可怜)
后端:按照约定的接口mock就行,直接给我抛出了一个proto文件
前端:此时已经一脸懵逼状态,proto是个啥?如何根据proto来mock一份数据?后端为什么要用proto,JSON不香吗?为了弥补上自己欠缺的一环,开启了Protobuf的救赎之路。
Protobuf 作为一种跨平台、语言无关、可扩展的序列化结构数据的方法,已广泛应用于网络数据交换及存储。其目前已经支持的开发语言有多种(C++、Java、Python、Objective-C、C#、JavaNano、JavaScript、Ruby、Go、PHP),详情可参考(https://github.com/52im/protobuf)。其具有如下优缺点:
优点
(1)序列化后体积小,适合网络传输
(2)支持跨平台、多语言
(3)具有较好的升级和兼容性(具有向后兼容的特性,更新数据结构以后,老版本依旧可以兼容)
(4)序列化和反序列化的速度较快
缺点
Protobuf是二进制协议,编码后的数据可读性差
Protobuf用法的使用有很多,本次就通过一个例子来看看其基本使用,具体使用可以在网上搜索相关文档进行学习。
syntax = "proto2"
package transferData;
message transferMessage {
required string name = 1;
required int32 age = 2;
enum SexEnum {
Boy = 0;
Girl = 1;
}
optional SexEnum SexEnum = 3;
}
syntax = "proto2";
该行用于指定语法版本,目前有两个版本proto2和proto3,两个版本不兼容,如果不指定,默认语法是proto2.
package transferData;
用于定义该包的包名;
message
message是Protobuf中最基本的数据单元,其中可以嵌套message或其它的基础数据类型的成员;
属性
message中的每一行就是一个属性,例如required string name = 1,其组成如下所示:
标注 | 类型 | 属性名 | 属性顺序号 | [options] |
---|---|---|---|---|
required | string | name | = 1 | 一些可选项 |
(1)标注有三种:
required:必选属性;
optional:可选属性;
repeated:重复字段,类似于动态数组;
(2)类型有多种,每种语言不同,例如:int32、int64、int、float、double、string等;
(3)属性名:用于表征该属性的名称;
(4)属性顺序号:protobuf为了提高数据的压缩和可选性等功能定义的,需要按照顺序进行定义,且不允许有重复;
(5)[options]:protobuf提供了一些内置的options可供选择想,可大大提高protobuf的扩展性。
enum
定义消息类型时,可能需要某字段值是一些预设值之一,此时枚举类型就能够发挥作用了。
注:protobuf还有很多用法,此处只做了简单介绍,有喜欢的同学可进一步自己深入学习。
聊了那么多,下面就进入实战环节,实战将在node运行环境下,构建TCP连接,然后由客户端发送经过Protobuf序列化的内容至服务端,然后服务端接收到信息之后进行解析,其中proto文件的序列化和反序列化将使用protobuf.js包,其是一个纯 JavaScript 实现,支持node.js和浏览器。它易于使用,速度极快,并且可以使用.proto文件开箱即用!(https://www.npmjs.com/package/protobufjs)
本次解析.proto文件使用的是protobuf.js包,常用的方法主要有以下几个:
load()
用该函数加载对应的.proto文件,加载完成之后才能够使用里面的message以及进行后续的操作;
lookupType()
在加载完.proto后,需要对使用的message进行初始化,即完成message实例化的过程;
verify()
该函数用于验证普通对象是某满足对应的message结构;
encode()
编码一个message实例或者可利用的普通js对象;
decode()
解码buffer至一个message实例,解码失败会排除错误;
create()
从一系列属性创建一个新的message实例,其优于通过fromObject创建,是由于其不会产生冗余的转换;
fromObject()
将任何无效的普通js对象转换为message实例;
toObject()
转换一个message实例去一个任意的普通js对象。
该库的使用还有一些其它方法,可以通过看其对应文档进行学习。对于上述转换关系如下图所示(来自于官方文档):
其是服务端,当接收到客户端发送的消息后,利用protobufjs库中的decode函数进行解析,获取解析后的结果。
const net = require('net');
const protobuf = require('protobufjs');
const decodeData = data => {
protobuf.load('./transfer.proto')
.then(root => {
const transferMessage = root.lookupType('transferData.transferMessage');
const result = transferMessage.decode(data);
console.log(result); // transferMessage { name: '狍狍', age: 1, sexEnum: 1 }
})
.catch(console.log);
}
const server = net.createServer(socket => {
socket.on('data', data =>{
decodeData(data);
});
socket.on('close', () => {
console.log('client disconnected!!!');
});
});
server.on('error', err => {
throw new Error(err);
});
server.listen(8081, () => {
console.log('server port is 8081');
});
其是客户端对应的代码,利用protobufjs库进行相应的操作,将序列化后的内容发送至服务端。
const net = require('net');
const protobuf = require('protobufjs');
const data = {
name: '狍狍',
age: 1,
sexEnum: 1
};
let client = new net.Socket();
client.connect({
port: 8081
});
client.on('connect', () => {
setMessage(data);
});
client.on('data', data => {
console.log(data);
client.end();
});
function setMessage(data) {
protobuf.load('./transfer.proto')
.then(root =>{
// 根据proto文件中的内容对message进行实例化
const transferMessage = root.lookupType('transferData.transferMessage');
// 验证
const errMsg = transferMessage.verify(data);
console.log('errMsg', errMsg);
if (errMsg) {
throw new Error(errMsg);
}
// 转换为message实例
const messageFromObj = transferMessage.fromObject(data);
console.log('messageFromObj', messageFromObj);
// 编码
const buffer = transferMessage.encode(messageFromObj).finish();
console.log(buffer);
// 发送
client.write(buffer);
})
.catch(console.log);
}
················· 执鸢者简介 ·················
你好,我是执鸢者,毕业于华中科技大学,现在是一名百度前端研发工程师,写有多篇原创文章,分享自己在学习中的点滴收获,近期撰写了《前端百题斩》系列文章,内容包括JS基础篇、浏览器篇、网络篇,共计50个章节,5万多字,该文章已帮助很多小伙伴夯实基础,找到心仪的工作。
金九银十求职季,前端点线面助你一臂之力,为广大求职者朋友们提供免费的简历修改服务,走过路过千万不要错过。
识别上方二维码加我微信、拉你进交流群
[1] 五万字前端面试宝典——前端百题斩(上)新鲜出炉