為了一份Mock數(shù)據(jù),開啟了Protobuf的救贖之路
一、背景
近期在做一個需求,該需求需要和后端進行交互,為了并行開發(fā),就跟后端產(chǎn)生了如下的對話:
前端:老鐵,可以給份mock數(shù)據(jù)嗎?
后端:mock數(shù)據(jù)太麻煩了,你自己來吧!!!
前端:我怎么知道數(shù)據(jù)長啥樣,如何mock呀!(可憐)
后端:按照約定的接口mock就行,直接給我拋出了一個proto文件
前端:此時已經(jīng)一臉懵逼狀態(tài),proto是個啥?如何根據(jù)proto來mock一份數(shù)據(jù)?后端為什么要用proto,JSON不香嗎?為了彌補上自己欠缺的一環(huán),開啟了Protobuf的救贖之路。
二、Protobuf是什么?
Protobuf 作為一種跨平臺、語言無關(guān)、可擴展的序列化結(jié)構(gòu)數(shù)據(jù)的方法,已廣泛應(yīng)用于網(wǎng)絡(luò)數(shù)據(jù)交換及存儲。其目前已經(jīng)支持的開發(fā)語言有多種(C++、Java、Python、Objective-C、C#、JavaNano、JavaScript、Ruby、Go、PHP),詳情可參考(https://github.com/52im/protobuf)。其具有如下優(yōu)缺點:
優(yōu)點
(1)序列化后體積小,適合網(wǎng)絡(luò)傳輸
(2)支持跨平臺、多語言
(3)具有較好的升級和兼容性(具有向后兼容的特性,更新數(shù)據(jù)結(jié)構(gòu)以后,老版本依舊可以兼容)
(4)序列化和反序列化的速度較快
缺點
Protobuf是二進制協(xié)議,編碼后的數(shù)據(jù)可讀性差
三、Protobuf的結(jié)構(gòu)
Protobuf用法的使用有很多,本次就通過一個例子來看看其基本使用,具體使用可以在網(wǎng)上搜索相關(guān)文檔進行學(xué)習(xí)。
- 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.
2.package transferData;
用于定義該包的包名;
3.message
message是Protobuf中最基本的數(shù)據(jù)單元,其中可以嵌套message或其它的基礎(chǔ)數(shù)據(jù)類型的成員;
4.屬性
message中的每一行就是一個屬性,例如required string name = 1,其組成如下所示:
標注 | 類型 | 屬性名 | 屬性順序號 | [options] |
---|---|---|---|---|
required | string | name | = 1 | 一些可選項 |
(1)標注有三種:
required:必選屬性;
optional:可選屬性;
repeated:重復(fù)字段,類似于動態(tài)數(shù)組;
(2)類型有多種,每種語言不同,例如:int32、int64、int、float、double、string等;
(3)屬性名:用于表征該屬性的名稱;
(4)屬性順序號:protobuf為了提高數(shù)據(jù)的壓縮和可選性等功能定義的,需要按照順序進行定義,且不允許有重復(fù);
(5)[options]:protobuf提供了一些內(nèi)置的options可供選擇想,可大大提高protobuf的擴展性。
5.enum
定義消息類型時,可能需要某字段值是一些預(yù)設(shè)值之一,此時枚舉類型就能夠發(fā)揮作用了。
注:protobuf還有很多用法,此處只做了簡單介紹,有喜歡的同學(xué)可進一步自己深入學(xué)習(xí)。
四、實戰(zhàn)
聊了那么多,下面就進入實戰(zhàn)環(huán)節(jié),實戰(zhàn)將在node運行環(huán)境下,構(gòu)建TCP連接,然后由客戶端發(fā)送經(jīng)過Protobuf序列化的內(nèi)容至服務(wù)端,然后服務(wù)端接收到信息之后進行解析,其中proto文件的序列化和反序列化將使用protobuf.js包,其是一個純 JavaScript 實現(xiàn),支持node.js和瀏覽器。它易于使用,速度極快,并且可以使用.proto文件開箱即用!(https://www.npmjs.com/package/protobufjs)
4.1 基本使用
本次解析.proto文件使用的是protobuf.js包,常用的方法主要有以下幾個:
1.load()
用該函數(shù)加載對應(yīng)的.proto文件,加載完成之后才能夠使用里面的message以及進行后續(xù)的操作;
2.lookupType()
在加載完.proto后,需要對使用的message進行初始化,即完成message實例化的過程;
3.verify()
該函數(shù)用于驗證普通對象是某滿足對應(yīng)的message結(jié)構(gòu);
4.encode()
編碼一個message實例或者可利用的普通js對象;
5.decode()
解碼buffer至一個message實例,解碼失敗會排除錯誤;
6.create()
從一系列屬性創(chuàng)建一個新的message實例,其優(yōu)于通過fromObject創(chuàng)建,是由于其不會產(chǎn)生冗余的轉(zhuǎn)換;
7.fromObject()
將任何無效的普通js對象轉(zhuǎn)換為message實例;
8.toObject()
轉(zhuǎn)換一個message實例去一個任意的普通js對象。
該庫的使用還有一些其它方法,可以通過看其對應(yīng)文檔進行學(xué)習(xí)。對于上述轉(zhuǎn)換關(guān)系如下圖所示(來自于官方文檔):
4.2 服務(wù)端
其是服務(wù)端,當接收到客戶端發(fā)送的消息后,利用protobufjs庫中的decode函數(shù)進行解析,獲取解析后的結(jié)果。
- 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 客戶端
其是客戶端對應(yīng)的代碼,利用protobufjs庫進行相應(yīng)的操作,將序列化后的內(nèi)容發(fā)送至服務(wù)端。
- 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 =>{
- // 根據(jù)proto文件中的內(nèi)容對message進行實例化
- const transferMessage = root.lookupType('transferData.transferMessage');
- // 驗證
- const errMsg = transferMessage.verify(data);
- console.log('errMsg', errMsg);
- if (errMsg) {
- throw new Error(errMsg);
- }
- // 轉(zhuǎn)換為message實例
- const messageFromObj = transferMessage.fromObject(data);
- console.log('messageFromObj', messageFromObj);
- // 編碼
- const buffer = transferMessage.encode(messageFromObj).finish();
- console.log(buffer);
- // 發(fā)送
- client.write(buffer);
- })
- .catch(console.log);
- }