共计 3579 个字符,预计需要花费 9 分钟才能阅读完成。
近期在做一个需要,该需要须要和后端进行交互,为了并行开发,就跟后端产生了如下的对话:
前端:老铁,能够给份 mock 数据吗?
后端:mock 数据太麻烦了,你本人来吧!!!
前端:我怎么晓得数据长啥样,如何 mock 呀!(可怜)
后端:依照约定的接口 mock 就行,间接给我抛出了一个 proto 文件
前端:此时曾经一脸懵逼状态,proto 是个啥?如何依据 proto 来 mock 一份数据?后端为什么要用 proto,JSON 不香吗?为了补救上本人欠缺的一环,开启了 Protobuf 的救赎之路。
二、Protobuf 是什么?
Protobuf 作为一种跨平台、语言无关、可扩大的序列化构造数据的办法,已广泛应用于网络数据交换及存储。其目前曾经反对的开发语言有多种(C++、Java、Python、Objective-C、C#、JavaNano、JavaScript、Ruby、Go、PHP),详情可参考(https://github.com/52im/protobuf)。其具备如下优缺点:
- 长处
(1)序列化后体积小,适宜网络传输
(2)反对跨平台、多语言
(3)具备较好的降级和兼容性(具备向后兼容的个性,更新数据结构当前,老版本仍旧能够兼容)
(4)序列化和反序列化的速度较快
- 毛病
Protobuf 是二进制协定,编码后的数据可读性差
三、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…)
4.1 根本应用
本次解析.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 对象。
该库的应用还有一些其它办法,能够通过看其对应文档进行学习。对于上述转换关系如下图所示(来自于官网文档):
4.2 服务端
其是服务端,当接管到客户端发送的音讯后,利用 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');
});
4.3 客户端
其是客户端对应的代码,利用 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);
}
1. 如果感觉这篇文章还不错,来个分享、点赞吧,让更多的人也看到
2. 欢送关注公众号 前端点线面, 一起学习“前端百题斩”,开启前端救赎之路。