2

为了一份mock数据,开启了Protobuf的救赎之路

 2 years ago
source link: https://segmentfault.com/a/1190000040632013
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.

近期在做一个需求,该需求需要和后端进行交互,为了并行开发,就跟后端产生了如下的对话:

前端:老铁,可以给份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;
}
  1. syntax = "proto2";

该行用于指定语法版本,目前有两个版本proto2和proto3,两个版本不兼容,如果不指定,默认语法是proto2.

  1. package transferData;

用于定义该包的包名;

  1. message

message是Protobuf中最基本的数据单元,其中可以嵌套message或其它的基础数据类型的成员;

message中的每一行就是一个属性,例如required string name = 1,其组成如下所示:

标注类型属性名属性顺序号[options]requiredstringname= 1一些可选项

(1)标注有三种:

required:必选属性;

optional:可选属性;

repeated:重复字段,类似于动态数组;

(2)类型有多种,每种语言不同,例如:int32、int64、int、float、double、string等;

(3)属性名:用于表征该属性的名称;

(4)属性顺序号:protobuf为了提高数据的压缩和可选性等功能定义的,需要按照顺序进行定义,且不允许有重复;

(5)[options]:protobuf提供了一些内置的options可供选择想,可大大提高protobuf的扩展性。

定义消息类型时,可能需要某字段值是一些预设值之一,此时枚举类型就能够发挥作用了。

注:protobuf还有很多用法,此处只做了简单介绍,有喜欢的同学可进一步自己深入学习。

聊了那么多,下面就进入实战环节,实战将在node运行环境下,构建TCP连接,然后由客户端发送经过Protobuf序列化的内容至服务端,然后服务端接收到信息之后进行解析,其中proto文件的序列化和反序列化将使用protobuf.js包,其是一个纯 JavaScript 实现,支持node.js和浏览器。它易于使用,速度极快,并且可以使用.proto文件开箱即用!(https://www.npmjs.com/package...

4.1 基本使用

本次解析.proto文件使用的是protobuf.js包,常用的方法主要有以下几个:

  1. load()

用该函数加载对应的.proto文件,加载完成之后才能够使用里面的message以及进行后续的操作;

  1. lookupType()

在加载完.proto后,需要对使用的message进行初始化,即完成message实例化的过程;

  1. verify()

该函数用于验证普通对象是某满足对应的message结构;

  1. encode()

编码一个message实例或者可利用的普通js对象;

  1. decode()

解码buffer至一个message实例,解码失败会排除错误;

  1. create()

从一系列属性创建一个新的message实例,其优于通过fromObject创建,是由于其不会产生冗余的转换;

  1. fromObject()

将任何无效的普通js对象转换为message实例;

  1. toObject()

转换一个message实例去一个任意的普通js对象。

该库的使用还有一些其它方法,可以通过看其对应文档进行学习。对于上述转换关系如下图所示(来自于官方文档):

protobuf.PNG

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.欢迎关注公众号前端点线面,一起学习“前端百题斩”,开启前端救赎之路。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK