首页 » SEO优化 » php获得googlebuffer数据技巧_在 NodeJS 中玩转 Protocol Buffer

php获得googlebuffer数据技巧_在 NodeJS 中玩转 Protocol Buffer

访客 2024-11-23 0

扫一扫用手机浏览

文章目录 [+]

1.3K

在这篇文章中:

php获得googlebuffer数据技巧_在 NodeJS 中玩转 Protocol Buffer

Protocol Buffer入门教程Protocol Buffer是个什么鬼?NodeJS开拓者为何要跟Protocol Buffer打交道在NodeJS中实践Protocol Buffer协议选择支持protobuf的NodeJS第三方模块一个栗子再举一个栗子其他高等特性总结一下优点缺陷

Protocol Buffer是个什么鬼?

php获得googlebuffer数据技巧_在 NodeJS 中玩转 Protocol Buffer
(图片来自网络侵删)

Protocol Buffer(下文简称protobuf)是Google供应的一种数据序列化协议,下面是我从网上找到的Google官方对protobuf的定义:

Protocol Buffers 是一种轻便高效的构造化数据存储格式,可以用于构造化数据序列化,很适宜做数据存储或 RPC 数据交流格式。
它可用于通讯协议、数据存储等领域的措辞无关、平台无关、可扩展的序列化构造数据格式。
目前供应了 C++、Java、Python 三种措辞的 API。

NodeJS开拓者为何要跟Protocol Buffer打交道

作为JavaScript开拓者,对我们最友好的数据序列化协议当然是大名鼎鼎的JSON啦!
我们本能的会想protobuf是什么鬼?还我JSON!

这就要说到protobuf的历史了。

Protobuf由Google出品,08年的时候Google把这个项目开源了,官方支持C++,Java,C#,Go和Python五种措辞,但是由于其设计得很大略,以是衍生出很多第三方的支持,基本上常用的PHP,C,Actoin Script,Javascript,Perl等多种措辞都已有第三方的库。

由于protobuf协议相较于之前盛行的XML更加的简洁高效(后面会提到这是为什么),因此许多后台接口都是基于protobuf定制的数据序列化协议。
而作为NodeJS开拓者,跟C++或JAVA编写的后台做事接口打交道那是家常便饭的事儿,因此我们很有必要节制protobuf协议。

为什么说利用利用类似protobuf的二进制协议通信更好呢?

二进制协议对付电脑来说更随意马虎解析,在解析速率上是http这样的文本协议不可比拟的。
有tcp和udp两种选择,在一些场景下,udp传输的效率会更高。
在后台开拓中,后台与后台的通信一样平常便是基于二进制协议的。
乃至某些native app和做事器的通信也选择了二进制协议(例如腾讯视频)。
但由于web前真个存在,后台同学每每须要特地开拓掩护一套http接口专供我们利用,如果web也能利用二进制协议,可以节省许多后台开拓的本钱。

在大公司,最主要的便是优化效率、节省本钱,因此二进制协议明显优于http这样的文本协议。

下面举两个大略的例子,该当有助于我们理解protobuf。

在NodeJS中实践Protocol Buffer协议

选择支持protobuf的NodeJS第三方模块

protobuf.js

Google protobuf js

protocol-buffers

根据star数和文档完善程度两方面综合考虑,我们决定选择protobuf.js。

一个栗子

我打算利用 Protobuf 和NodeJS开拓一个十分大略的例子程序。

该程序由两部分组成。
第一部分被称为 Writer,第二部分叫做 Reader。

Writer 卖力将一些构造化的数据写入一个磁盘文件,Reader 则卖力从该磁盘文件中读取构造化数据并打印到屏幕上。

准备用于演示的构造化数据是 HelloWorld,它包含两个基本数据:

ID,为一个整数类型的数据Str,这是一个字符串

书写.proto文件

首先我们须要编写一个 proto 文件,定义我们程序中须要处理的构造化数据,在 protobuf 的术语中,构造化数据被称为 Message。
proto 文件非常类似 java 或者 C 措辞的数据定义。
代码清单 1 显示了例子运用中的 proto 文件内容。

清单 1. proto 文件

package lm; message helloworld { required int32 id = 1; // ID required string str = 2; // str optional int32 opt = 3; //optional field }

一个比较好的习气是负责对待 proto 文件的文件名。
比如将命名规则定于如下:

packageName.MessageName.proto

在上例中,package 名字叫做 lm,定义了一个 helloworld,该有三个成员,类型为 int32 的 id,另一个为类型为 string 的成员 str。
opt 是一个可选的成员,即中可以不包含该成员。
1、2、3这几个数字是这三个字段的唯一标识符,这些标识符是用来在的二进制格式中识别各个字段的,一旦开始利用就不能够再改变。

编译 .proto 文件

我们可以利用protobuf.js供应的命令行工具来编译 .proto 文件

用法:

# pbjs <filename> [options] [> outFile]

我们来看看options:

--help, -h Show help [boolean] 查看帮助 --version, -v Show version number [boolean] 查看版本号 --source, -s Specifies the source format. Valid formats are: json Plain JSON descriptor proto Plain .proto descriptor指定来源文件格式,可以是json或proto文件 --target, -t Specifies the target format. Valid formats are: amd Runtime structures as AMD module commonjs Runtime structures as CommonJS module js Runtime structures json Plain JSON descriptor proto Plain .proto descriptor指定天生文件格式,可以是符合amd或者commonjs规范的js文件,或者是纯挚的js/json/proto文件。
--using, -u Specifies an option to apply to the volatile builder loading the source, e.g. convertFieldsToCamelCase. --min, -m Minifies the output. [default: false] 压缩天生文件 --path, -p Adds a directory to the include path. --legacy, -l Includes legacy descriptors from google/protobuf/ if explicitly referenced. [default: false] --quiet, -q Suppresses any informatory output to stderr. [default: false] --use, -i Specifies an option to apply to the emitted builder utilized by your program, e.g. populateAccessors. --exports, -e Specifies the namespace to export. Defaults to export the root namespace. --dependency, -d Library dependency to use when generating classes. Defaults to 'protobufjs' for CommonJS, 'ProtoBuf' for AMD modules and 'dcodeIO.ProtoBuf' for classes.

重点关注- -target就好,由于我们是在Node环境中利用,因此选择天生符合commonjs规范的文件,命令如下:

# ./pbjs ../../lm.message.proto -t commonjs > ../../lm.message.js

得到编译后的符合commonjs规范的js文件:

module.exports = require(\"大众protobufjs\公众).newBuilder({})['import']({ \"大众package\公众: \"大众lm\公众, \"大众messages\"大众: [ { \"大众name\公众: \"大众helloworld\"大众, \"大众fields\"大众: [ { \公众rule\"大众: \"大众required\"大众, \"大众type\"大众: \公众int32\公众, \"大众name\公众: \"大众id\"大众, \"大众id\公众: 1 }, { \公众rule\"大众: \"大众required\公众, \"大众type\公众: \"大众string\"大众, \"大众name\"大众: \公众str\公众, \"大众id\"大众: 2 }, { \公众rule\"大众: \"大众optional\"大众, \公众type\公众: \公众int32\"大众, \公众name\公众: \公众opt\公众, \公众id\"大众: 3 } ] } ]}).build();

编写 Writer

var HelloWorld = require('./lm.helloworld.js')['lm']['helloworld'];var fs = require('fs');// 除了这种传入一个工具的办法, 你也可以利用get/set 函数用来修正和读取构造化数据中的数据成员var hw = new HelloWorld({ 'id': 101, 'str': 'Hello'})var buffer = hw.encode();fs.writeFile('./test.log', buffer.toBuffer(), function(err) { if(!err) { console.log('done!'); }});

编写Reader

var HelloWorld = require('./lm.helloworld.js')['lm']['helloworld'];var fs = require('fs');var buffer = fs.readFile('./test.log', function(err, data) { if(!err) { console.log(data); // 来看看Node里的Buffer工具长什么样子。
var message = HelloWorld.decode(data); console.log(message); }})

运行结果

由于我们没有在Writer中给可选字段opt字段赋值,因此Reader读出来的opt字段值为null。

这个例子本身并无意义,但只要您稍加修正就可以将它变成更加有用的程序。
比如将磁盘更换为网络 socket,那么就可以实现基于网络的数据交流任务。
而存储和交流正是 Protobuf 最有效的运用领域。

再举一个栗子

俗话说得好:“天下上没有什么技能问题是不能用一个helloworld的栗子阐明清楚的,如果弗成,那就用两个!

在这个栗子中,我们来实现基于网络的数据交流任务。

编写.proto

cover.helloworld.proto文件:

package cover;message helloworld { message helloCoverReq { required string name = 1; } message helloCoverRsp { required int32 retcode = 1; optional string reply = 2; }}

编写client

一样平常情形下,利用 Protobuf 的人们都会先写好 .proto 文件,再用 Protobuf 编译器天生目标措辞所须要的源代码文件。
将这些天生的代码和运用程序一起编译。

可是在某些情形下,人们无法预先知道 .proto 文件,他们须要动态处理一些未知的 .proto 文件。
比如一个通用的转发中间件,它不可能预知须要处理若何的。
这须要动态编译 .proto 文件,并利用个中的 Message。

我们这里决定利用protobuf文件可以动态编译的特性,在代码中直接读取proto文件,动态天生我们须要的commonjs模块。

client.js

var dgram = require('dgram');var ProtoBuf = require(\"大众protobufjs\公众);var PORT = 33333;var HOST = '127.0.0.1';var builder = ProtoBuf.loadProtoFile(\"大众./cover.helloworld.proto\"大众), Cover = builder.build(\"大众cover\"大众), HelloCoverReq = Cover.helloworld.helloCoverReq; HelloCoverRsp = Cover.helloworld.helloCoverRsp;var hCReq = new HelloCoverReq({ name: 'R U coverguo?'})var buffer = hCReq.encode();var socket = dgram.createSocket({ type: 'udp4', fd: 8080}, function(err, message) { if(err) { console.log(err); } console.log(message);});var message = buffer.toBuffer();socket.send(message, 0, message.length, PORT, HOST, function(err, bytes) { if(err) { throw err; } console.log('UDP message sent to ' + HOST +':'+ PORT);});socket.on(\公众message\"大众, function (msg, rinfo) { console.log(\"大众[UDP-CLIENT] Received message: \"大众 + HelloCoverRsp.decode(msg).reply + \"大众 from \"大众 + rinfo.address + \公众:\"大众 + rinfo.port); console.log(HelloCoverRsp.decode(msg)); socket.close(); //udpSocket = null;});socket.on('close', function(){ console.log('socket closed.');});socket.on('error', function(err){ socket.close(); console.log('socket err'); console.log(err);});

书写server

server.js

var PORT = 33333;var HOST = '127.0.0.1';var ProtoBuf = require(\"大众protobufjs\公众);var dgram = require('dgram');var server = dgram.createSocket('udp4');var builder = ProtoBuf.loadProtoFile(\"大众./cover.helloworld.proto\"大众), Cover = builder.build(\公众cover\"大众), HelloCoverReq = Cover.helloworld.helloCoverReq; HelloCoverRsp = Cover.helloworld.helloCoverRsp;server.on('listening', function () { var address = server.address(); console.log('UDP Server listening on ' + address.address + \公众:\公众 + address.port);});server.on('message', function (message, remote) { console.log(remote.address + ':' + remote.port +' - ' + message); console.log(HelloCoverReq.decode(message) + 'from client!'); var hCRsp = new HelloCoverRsp({ retcode: 0, reply: 'Yeah!I\'m handsome cover!' }) var buffer = hCRsp.encode(); var message = buffer.toBuffer(); server.send(message, 0, message.length, remote.port, remote.address, function(err, bytes) { if(err) { throw err; } console.log('UDP message reply to ' + remote.address +':'+ remote.port); })});server.bind(PORT, HOST);

运行结果

其他高等特性

嵌套Message

message Person { required string name = 1; required int32 id = 2; // Unique ID number for this person. optional string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { required string number = 1; optional PhoneType type = 2 [default = HOME]; } repeated PhoneNumber phone = 4; }

在 Message Person 中,定义了嵌套 PhoneNumber,并用来定义 Person 中的 phone 域。
这使得人们可以定义更加繁芜的数据构造。

Import Message

在一个 .proto 文件中,还可以用 Import 关键字引入在其他 .proto 文件中定义的,这可以称做 Import Message,或者 Dependency Message。

比如下例:

import common.header; message youMsg{ required common.info_header header = 1; required string youPrivateData = 2; }

个中 ,common.info_header定义在common.header包内。

Import Message 的用途紧张在于供应了方便的代码管理机制,类似 C 措辞中的头文件。
您可以将一些公用的 Message 定义在一个 package 中,然后在别的 .proto 文件中引入该 package,进而利用个中的定义。

Google Protocol Buffer 可以很好地支持嵌套 Message 和引入 Message,从而让定义繁芜的数据构造的事情变得非常轻松愉快。

总结一下

优点

大略说来 Protobuf 的紧张优点便是:简洁,快。

为什么这么说呢?

简洁

由于Protocol Buffer 信息的表示非常紧凑,这意味着的体积减少,自然须要更少的资源。
比如网络上传输的字节数更少,须要的 IO 更少等,从而提高性能。

对付代码清单 1 中的,用 Protobuf 序列化后的字节序列为:

08 65 12 06 48 65 6C 6C 6F 77

而如果用 XML,则类似这样:

31 30 31 3C 2F 69 64 3E 3C 6E 61 6D 65 3E 68 65 6C 6C 6F 3C 2F 6E 61 6D 65 3E 3C 2F 68 65 6C 6C 6F 77 6F 72 6C 64 3E

一共 55 个字节,这些奇怪的数字须要轻微阐明一下,其含义用 ASCII 表示如下:

<helloworld> <id>101</id> <name>hello</name> </helloworld>

我相信与XML一样同为文本序列化协议的JSON也不会好到哪里去。

首先我们来理解一下 XML 的封解包过程。
XML 须要从文件中读取出字符串,再转换为 XML 文档工具构造模型。
之后,再从 XML 文档工具构造模型中读取指定节点的字符串,末了再将这个字符串转换成指定类型的变量。
这个过程非常繁芜,个中将 XML 文件转换为文档工具构造模型的过程常日须要完成词法文法剖析等大量花费 CPU 的繁芜打算。

反不雅观 Protobuf,它只须要大略地将一个二进制序列,按照指定的格式读取到编程措辞对应的构造类型中就可以了。
而的 decoding 过程也可以通过几个位移操作组成的表达式打算即可完成。
速率非常快。

缺陷

作为二进制的序列化协议,人眼不可读!
标签:

相关文章

介绍百度网盘,云端存储时代的创新先锋

随着互联网技术的飞速发展,云计算已经成为现代生活不可或缺的一部分。而在这其中,百度网盘作为国内领先的云存储服务提供商,以其卓越的性...

SEO优化 2025-01-03 阅读1 评论0

介绍监控屏蔽技术,守护个人隐私的利器

随着科技的发展,监控设备已经深入到我们生活的方方面面。在享受便利的隐私安全问题也日益凸显。如何有效屏蔽监控,保护个人隐私,成为人们...

SEO优化 2025-01-03 阅读1 评论0

介绍番号观看方法,轻松驾驭影视世界

随着互联网的普及,网络影视资源日益丰富,番号作为影视作品的标识码,已经成为广大观众了解、搜索和观看影视作品的重要途径。如何正确地使...

SEO优化 2025-01-03 阅读1 评论0

介绍盗微信号黑幕,网络安全的严峻挑战

在数字化时代,微信已成为人们生活中不可或缺的通讯工具。随着微信用户数量的激增,盗微信号的事件也日益增多。本文将深入剖析盗微信号的方...

SEO优化 2025-01-03 阅读1 评论0