关于protobuf:protobuf2-消息类型

根本数据类型syntax = "proto3";// 所有根本数据类型// protoc --go_out=. scalar.protooption go_package = "../service";message scalar{ double filed1 = 1; //float64 float field2 = 2; //float32 int32 field3 = 3; //int32 int64 field4 = 4; //int64 uint32 field5 = 5; //uint32 uint64 field6 = 6; //uint64 sint32 field7 = 7; //int32 sint64 field8 = 8; //int64 fixed32 field9 = 9; //uint32 fixed64 field10 = 10; //uint64 sfixed32 field11 = 11; //int32 sfixed64 field12 = 12; //int64 bool field13 = 13; //bool string field14 = 14; //string bytes field15 = 15; //[]byte}生成的go代码 ...

August 5, 2022 · 2 min · jiezi

关于protobuf:protobuf1安装

装置protoc依据本人的服务器版本进行下载安装,下载地址 https://github.com/protocolbu... $ uname -aLinux localhost.localdomain 3.10.0-229.el7.x86_64 #1 SMP Fri Mar 6 11:36:42 UTC 2015 x86_64 x86_64 x86_64 GNU/Linux$ wget wget https://github.com/protocolbuffers/protobuf/releases/download/v21.4/protoc-21.4-linux-x86_64.zip$ unzip -d protoc-21.4-linux-x86_64 protoc-21.4-linux-x86_64.zip$ cd protoc-21.4-linux-x86_64/bin/$ mv protoc /usr/local/bin// 查看是否装置胜利$ protoc --versionlibprotoc 3.21.4装置protoc-gen-gogo语言中将.proto 转化为go代码 go get -u github.com/golang/protobuf/protoc-gen-go测试代码https://github.com/tim1116/go...

August 3, 2022 · 1 min · jiezi

关于protobuf:API-工程化分享

概要本文是学习B站毛剑老师的《API 工程化分享》的学习笔记,分享了 gRPC 中的 Proto 治理形式,Proto 分仓源码形式,Proto 独立同步形式,Proto git submodules 形式,Proto 我的项目布局,Proto Errors,服务端和客户端的 Proto Errors,Proto 文档等等 目录Proto IDL ManagementIDL Project LayoutIDL ErrorsIDL DocsProto IDL ManagementProto IDLProto 治理形式Proto 分仓源码形式Proto 独立同步形式Proto git submodules 形式Proto IDLgRPC 从协定缓冲区应用接口定义语言 (IDL)。协定缓冲区 IDL 是一种与平台无关的自定义语言,具备凋谢标准。 开发人员会创作 .proto 文件,用于形容服务及其输出和输入。 而后,这些 .proto 文件可用于为客户端和服务器生成特定于语言或平台的存根,使多个不同的平台可进行通信。 通过共享 .proto 文件,团队可生成代码来应用彼此的服务,而无需采纳代码依赖项。 Proto 治理形式煎鱼的一篇文章:真是头疼,Proto 代码到底放哪里? 文章中通过多轮探讨对 Proto 的存储形式和对应带来的优缺点,一共有如下几种计划: 代码仓库独立仓库集中仓库镜像仓库镜像仓库 在我本人的微服务仓库外面,有一个 Proto 目录,就是放我本人的 Proto,而后在我提交我的微服务代码到骨干或者某个分支的时候,它可能触发一个 mirror 叫做主动同步,会镜像到这个集中的仓库,它会帮你复制过来,相当于说我不须要把我的源码的 Proto 凋谢给你,同时还会主动复制一份到集中的仓库 在煎鱼的文章外面的集中仓库还是分了仓库的,B站大仓是一个对立的仓库。为什么呢?因为比如像谷歌云它整个对外的 API 会在一个仓库,不然你让用户怎么找?到底要去哪个 GitHub 上来找?有这么多 project 怎么找?基本找不到,应该建对立的一个仓库,一个我的项目就搞定了 咱们最早衍生这个想法是因为无心中看到了 Google APIs 这个仓库。大仓能够解决很多问题,包含高度代码共享,其实对于 API 文件也是一样的,集中在一个 Repo 外面,很不便去检索,去查阅,甚至看文档,都很不便 ...

May 15, 2022 · 4 min · jiezi

关于protobuf:Protobuf通信协议

前言在挪动互联网时代,手机流量、电量是最为无限的资源,而挪动端的即时通讯利用无疑必须得直面这两点。 解决流量过大的根本办法就是应用高度压缩的通信协议,而数据压缩后流量减小带来的天然后果也就是省电:因为大数据量的传输必然须要更久的网络操作、数据序列化及反序列化操作,这些都是电量耗费过快的本源。 以后即时通讯利用中最热门的通信协议无疑就是Google的Protobuf了,基于它的优良体现,微信和手机QQ这样的支流IM利用也早已在应用它。 Protobuf简介什么是 Google Protocol Buffer? 如果您在网上搜寻,应该会失去相似这样的文字介绍: Google Protocol Buffer( 简称 Protobuf) 是 Google 公司外部的混合语言数据规范,目前曾经正在应用的有超过 48,162 种报文格式定义和超过 12,183 个 .proto 文件。他们用于 RPC 零碎和继续数据存储系统。 Protocol Buffers 是一种轻便高效的结构化数据存储格局,能够用于结构化数据串行化,或者说序列化。它很适宜做数据存储或 RPC 数据交换格局。可用于通信协定、数据存储等畛域的语言无关、平台无关、可扩大的序列化构造数据格式。目前提供了 C++、Java、Python 三种语言的 API(即时通讯网注:Protobuf官网工程主页上显示的已反对的开发语言多达10种,别离有:C++、Java、Python、Objective-C、C#、JavaNano、JavaScript、Ruby、Go、PHP,基本上支流的语言都已反对,详见工程主页:https://github.com/52im/protobuf)。 装置 Google Protocol Buffer在网站 https://github.com/52im/protobuf 上能够下载 Protobuf 的源代码。而后解压编译装置便能够应用它了。 装置步骤如下所示: tar -xzf protobuf-2.1.0.tar.gz cd protobuf-2.1.0 ./configure --prefix=$INSTALL_DIR makemake check make install和其余相似技术的比拟简略说来 Protobuf 的次要长处就是:简略,快。 Total Time 指一个对象操作的整个工夫,包含创建对象,将对象序列化为内存中的字节序列,而后再反序列化的整个过程。从测试后果能够看到 Protobuf 的问题很好,感兴趣的读者能够自行到网站 https://github.com/eishay/jvm...上理解更具体的测试后果。

April 19, 2022 · 1 min · jiezi

关于protobuf:Proto-代码到底放哪里

尽管公司曾经从大单体切换为微服务化有肯定的年头了,但一些细节方面的解决总会有不同的人有不同的认识,这其中一个探讨点,就是 Proto 这个 IDL 的代码到底放在哪里? 目前来看,一共有如下计划, 咱们一起来探讨一下 Proto 的存储形式和对应带来的优缺点。 计划一:寄存在代码仓库间接将我的项目所依赖到的所有 Proto 文件都寄存在 proto/ 目录下,不通过开发工具的主动拉取和公布: 毛病我的项目所有依赖的 Proto 都存储在代码仓库下,因而所有依赖 Proto 都须要人工的向其它业务组 “要” 来,再放到 proto/ 目录下,人工染指极度麻烦。Proto 降级和变更,常常要反复第一步,沟通老本高。长处我的项目所有依赖的 Proto 都存储在代码仓库下,因而不波及集体开仓库权限的问题。多 Proto 的切换开销缩小,因为都在代码仓库下,不须要看这看那。计划二:独立仓库独立仓库存储是咱们最早采取的形式,也就是每个服务对应配套一个 Proto 仓库: 这个计划的益处就是能够独立治理所有 Proto 仓库,并且权限划分清晰。但最大的长处也是最大的毛病,因为一个服务会依赖多个 Proto 仓库,并且存在跨业务组调用的状况: 如上图所示,svc-user 服务别离依赖了三块 Proto 仓库,别离是本人组的、业务组 A、业务组 B 总共的 6 个 Proto 仓库。 毛病假如你是一个新入职的开发人员,那么你就须要找不同的业务组申请不同的仓库权限,十分麻烦。如果没有批量赋权工具,也没有管理者权限,那么就须要一个个赋权,十分麻烦。在运行服务的时候,你须要将所有相关联的 Proto 仓库拉取下来,如果没有工具做半自动化的反对,麻烦水平无法忍受。长处使得安全性较高(但 IDL 自身没有太多的机密)。按需拉取,不须要关注其余的服务 Proto。计划三:集中仓库集中仓库也是一些公司思考的形式之一,是按公司或大事业部的维度进行 Proto 代码的存储,这样子只须要拉取一个仓库,就能够获取到所有所需的 IDL: 毛病安全性降落,因为其它业务组的 IDL 也全都 “泄露” 了。非按需拉取,在查看原始文件时,须要关注一些多余的。长处只须要拉取一次 Proto 仓库就能够轻松把一个服务所需的 IDL 集齐。仓库权限治理的复杂度降落。计划四:镜像仓库联合下面几种计划的特点,咱们也能够得出镜像仓库的形式,也就是本人服务的 Proto 文件寄存在代码仓库的 proto 文件中,在本次 feature 提交或公布后,主动同步到镜像仓库去。 ...

December 9, 2021 · 1 min · jiezi

关于protobuf:•-a-gopackage-option-in-the-proto-source-file

接着上一章讲,在编写将proto文件生成pb文件脚本提醒一下无关go_package谬误问题,问题详情如下所示。 Please specify either: • a "go_package" option in the .proto source file, or • a "M" argument on the command line.这是因为在 proto3 的语法中短少了 option go_package。 解决方案:在syntax="proto3";下一行增加option go_package配置项。 option go_package = "ofc_app;pb_ofc_app_v1";go_package是有两局部组成,这两局部是由";"隔开的。前边局部示意生成pb文件的门路,后边局部示意pb文件的包名。 最初在protobuf下生成了ofc_app文件夹,以及对应的pb文件。关上生成pb文件,包名即为go_package后半局部内容。

November 12, 2021 · 1 min · jiezi

关于protobuf:protocol-buffer的高效编码方式

简介protocol buffer这种优良的编码方式,到底底层是怎么工作的呢?为什么它能够实现高效疾速的数据传输呢?这所有都要从它的编码方式说起。 定义一个简略的message咱们晓得protocol buffer的主体就是message,接下来咱们从一个简略的message登程,具体解说protobuf中的编码方式。 比方上面的一个非常简单的音讯对象: message Student { optional int32 age = 1;}在下面的例子中,咱们定义了一个Student音讯对象,并给他定义了一个名叫age的字段,并给它设置一个值叫做22。而后应用protobuf将其进行序列化,这么大的一个对象,对其序列化之后的字节如下所示: 08 96 00很简略,应用三个字节就能够示意一个messag对象,数据量十分小。 那么这三个字节到底示意什么意思呢?一起来看看吧 。 Base 128 Varints在解释下面的三个字节的含意之前,咱们须要理解一个varints的概念。 什么叫Varints呢?就是序列化整数的时候,占用的空间大小是不一样的,小的整数占用的空间小,大的整数占用的空间大,这样不必固定一个具体的长度,能够缩小数据的长度,然而会带来解析的复杂度。 那么怎么晓得这个数据到底须要几个byte呢?在protobuf中,每个byte的最高位是一个判断位,如果这个位被置位1,则示意前面一个byte和该byte是一起的,示意同一个数,如果这个位被置位0,则示意前面一个byte和该byte没有关系,数据到这个byte就完结了。 举个例子,一个byte是8位,如果示意的是整数1,那么能够用上面的byte来示意: 0000 0001如果一个byte装不下的整数,那么就须要应用多个byte来进行连贯操作,比方上面的数据表示的是300: 1010 1100 0000 0010为什么是300呢?首先看第一个byte,它的首位是1,示意前面还有一个byte。再看第二个byte,它的首位是0,示意到此就完结了。咱们把判断位去掉,变成上面的数字: 010 1100 000 0010这时候还不能计算数据的值,因为在protobuf中,byte的位数是反过来的,所以咱们须要把下面的两个byte替换一下地位: 000 0010 010 1100 也就是: 10 010 1100 =256 + 32 + 8 + 4 = 300 音讯体的构造从message的定义能够晓得,protobuf中的音讯体的构造是key=value的模式,其中的key就是message中定义的字段的整数值1,2,3,4等。而value就是真正对其设置的值。 当一个音讯被编码之后,这些key和value会被连贯在一起,组成一个byte stream。当要对其进行解析的时候,须要定位到key和value的具体长度,所以在key中须要蕴含两局部,第一个局部就是字段在proto文件中的值,第二个局部就是value局部占用的长度大小。 只有通过这两个局部的值联合起来,解析器才可能正确的对字段进行解析。 key的这种格局,被称为 wire types,有哪些 wire types呢?咱们看一下: 类型含意应用场景0Varintint32, int64, uint32, uint64, sint32, sint64, bool, enum164-bitfixed64, sfixed64, double2Length-delimitedstring, bytes, embedded messages, packed repeated fields3Start groupgroups (deprecated)4End groupgroups (deprecated)532-bitfixed32, sfixed32, float能够看到除了3,4两种类型之外,其余的类型能够分为三类,一类是固定长度的类型,如1,5,他们别离是64位和32位的数字。 ...

August 24, 2021 · 1 min · jiezi

关于protobuf:抖音直播protobuf

protobuf 之前有说过,就不再反复了。可回顾《万方protobuf协定申请案例》。 原文链接:https://blog.csdn.net/weixin_... 查看 common-utils-message.979d96f7.js 中的办法 var d = (new Map). set("WebcastDiggMessage", "DiggMessage"). set("WebcastFansclubMessage", "FansclubMessage"). set("WebcastScreenChatMessage", "ScreenChatMessage"). set("WebcastControlMessage", "ControlMessage"). set("WebcastSocialMessage", "SocialMessage"). set("WebcastRoomMessage", "RoomMessage"). set("WebcastFansclubStatisticsMessage", "FansclubStatisticsMessage" set("WebcastRoomVerifyMessage", "RoomVerifyMessage"). set("WebcastNoticeMessage", "NoticeMessage"). set("WebcastNobleUpgradeMessage", "NobleUpgradeMessage"). set("WebcastImDeleteMessage", "ImDeleteMessage"). set("WebcastSunDailyRegionRankMessage","SunDailyRankMessage"). set("WebcastSunDailyRankMessage", "SunDailyRankMessage"). set("WebcastRoomUserSeqMessage", "RoomUserSeqMessage"). set("WebcastVerificationCodeMessage", "VerificationCodeMessage"). set("WebcastLinkMicMethod", "LinkMicMethod"). set("WebcastLinkMicBattleMethod", "LinkMicBattle") .set("WebcastLinkMicBattleFinishMethod", "LinkMicBattleFinish"). set("WebcastRoomNotifyMessage", "NotifyMessage"). set("WebcastLinkMicSignalingMethod", "LinkMicSignalingMethod"). set("WebcastLotteryEventMessage", "LotteryEventMessage"). set("WebcastUploadCoverMessage", "UploadCoverMessage"). set("WebcastCategoryChangeMessage", "CategoryChangeMessage"). set("WebcastRoomChallengeMessage", "RoomChallengeMessage"). set("WebcastLikeMessage", "LikeMessage"). set("WebcastLinkMicArmiesMethod", "LinkMicArmies"). set("WebcastGameInviteMessage", "GameInviteMessage"). set("WebcastQuizStartMessage", "QuizStartMessage"). set("WebcastQuizResultMessage", "QuizResultMessage"). set("WebcastQuizChangeMessage", "QuizChangeMessage"). set("WebcastQuizBeginMessage", "QuizBeginMessage"). set("WebcastOChannelAnchorMessage", "OChannelAnchorMessage"). set("WebcastOChannelModifyMessage", "OChannelModifyMessage"). set("WebcastLinkMicOChannelKickOutMsg", "LinkMicOChannelKickOutMsg"). set("WebcastLinkMicOChannelNotifyMsg", "LinkMicOChannelNotifyMsg"). set("WebcastRoomIntroMessage", "RoomIntroMessage"). set("WebcastGroupShowUserUpdateMessage", "GroupShowUserUpdateMessage");依据Js中的代码,定义proto文件 ...

August 13, 2021 · 1 min · jiezi

关于protobuf:Protocol-Buffers一款比xml快100倍的序列化框架

咱们通常习惯用Json、XML等模式的数据存储格局,但置信还有很多人没有据说过Protocol Buffer(简称protobuf)。protobuf是Google开源的一个语言无关、平台无关的通信协议,其玲珑、高效和敌对的兼容性设计,使其被宽泛应用。性能比Json、XML真的强太多了! 而且,随着微服务架构的风行,RPC框架也成为服务框架的重要组成部分。在很多RPC的设计中,都采纳了高性能的编解码技术,而protobuf就属于其中的佼佼者。 也就说,要想深刻理解微服务架构中的RPC环节底层实现,设计出高效的传输、序列化、编码解码等性能,学习protobuf的应用和原理十分有必要。 protobuf简介protobuf是一种序列化对象框架(或者说是编解码框架)。它有两局部性能组成:结构化数据(数据存储构造)和序列化&反序列化。 其中数据存储构造的作用与XML、JSON类似;序列化和反序列化的作用与Java自带的序列化、Facebook的Thrift和JBoss Marshalling等类似。 总之:protobuf是通过定义结构化数据,并提供对数据的序列化和反序列化性能,从而实现数据存储/RPC数据交换的性能。 它的特点是: 语言无关、平台无关简洁高性能(序列化速度快 & 序列化后的数据体积小)良好的兼容性能够通过数据直观的看一下不同框架在序列化响应工夫上的比照: 能够看出,protobuf的性能要远高于其余框架。 protobuf的应用流程下面介绍了protobuf的性能,但仅仅晓得这些性能咱们无奈晓得它是怎么应用的。看了网上很多的文章,要么间接开始写代码要么间接开始剖析报文格式,对于老手来说往往会一头雾水。 所以,咱们先来梳理一下应用protobuf的步骤。 在上图中将protobuf的应用分了四个步骤: 步骤一,搭建环境:应用protobuf要定义通信的数据结构,并编译生成不同的编程语言代码,这就须要有这么一个编译器的环境。步骤二,构建数据:应用protobuf是要传输数据的,那么数据蕴含什么,有哪些项目,整个构造档次是什么样子的。这里基于protobuf的语法来进行数据结构的定义。步骤三,我的项目集成:集成pom依赖(Java为例)、集成编译的Java类(对照proto文件);步骤四,具体应用:通过集成进来的Java类,来构建音讯、赋值,而后基于protobuf进行序列化,接管方进行反序列化操作;理解了上述步骤,上面就针对具体的步骤来进行实战演示。 这里演示基于Mac OS操作系统和Java编程语言来进行操作。如果你应用的是其余操作系统和编程语言,基本思路一样,在不同的步骤时可针对性的找一下具体操作。 装置Protocol Buffers装置protobuf是为了进行数据结构的定义和对应编程语言代码的生成。通常有两种形式:本地装置和IDE插件。咱们先来看本地装置。 protobuf的代码是托管在GitHub上的,对应地址为:https://github.com/protocolbu... 。 点击我的项目左边的release链接可看到对应版本:https://github.com/protocolbu... 。 这里蕴含了各种编程语言、环境的版本。本文选protobuf-java-3.17.3.zip版本。 在Mac操作系统下,须要先装置一下依赖组件,才可能对protobuf进行编译和装置。 装置依赖组件: // 装置 Protocol Buffer依赖// 注:Protocol Buffer依赖于autoconf、automake、libtool、curlbrew install autoconf automake libtool curl解压protobuf-java-3.17.3.zip,进入根目录,执行以下命令: // 运行autogen.sh脚本./autogen.sh// 运行configure.sh脚本./configure // 编译未编译的依赖包make // 查看依赖包是否残缺make check // 开始装置Protocol Buffermake install装置实现,测验版本: $protoc --versionlibprotoc 3.14.0输入版本信息,阐明装置胜利。 这里的protoc命令就是Protocol Buffer的编译器,能够将 .proto文件编译成对应平台的头文件和源代码文件。 另外一种形式就是装置IDE插件,这里以IDEA为例,搜寻插件: 对于protobuf的插件比拟多,抉择适宜本人就行。 而后gRPC官网举荐了一种更优雅的应用姿态,能够通过maven轻松搞定(需装置上图中的“Protobuf Support”插件)。也就是引入grpc的一些组件,而后在maven的build中进行配置,编译proto文件成为Java代码。此种形式临时不开展,后续可间接看我的项目集成局部的源代码。 构建数据在Java中,如果通过JSON来传输一个数据,咱们首先要定义一个对象,这里以Person为例: public class Person { private String name; private Integer id; // ... getter/setter}那么,如果用protobuf来定义Person这个对象的数据结构是什么样呢? ...

August 3, 2021 · 2 min · jiezi

关于protobuf:gozero框架工具安装

一、goctl装置运行如下命令 GO111MODULE=on GOPROXY=https://goproxy.cn/,direct go get -u github.com/tal-tech/go-zero/tools/goctl配置文件 sudo vim /etc/paths在最初一行增加$GOPATH/bin二、protoc-gen-go装置在$GOPATH下的src目录下下载protobuf源码:git clone mailto:git@github.com:protocolbuffers/protobuf.git进入到protoc-gen-go目录下,执行如下go build和go install命令。执行命令后会在$GOPATH/bin目录下主动生成protoc-gen-go可执行文件。如果源码不在src目录下,生成protoc-gen-go可执行文件后须要将该文件移到$GOPATH/bin目录下,否则会报找不到可执行文件的谬误。三、protoc装置下载源码:https://github.com/protocolbu...解压后进入到源码下的bin目录,找到protoc二进制文件,把protoc文件挪动到$GOPATH/bin目录执行protoc --version查看时候装置胜利

July 20, 2021 · 1 min · jiezi

关于protobuf:Mac上使用protoc编译报错

执行protoc -I=. --go_out=paths=source_relative:gen/go trip.proto命令报错, 错误信息如下: protoc-gen-go: program not found or is not executablePlease specify a program using absolute path or make sure the program is available in your PATH system variable--go_out: protoc-gen-go: Plugin failed with status code 1.解决方案:1.找到protoc-gen-go 文件,执行以下命令,该文件通过go get装置时会装置在$GOPATH/go/bin目录下cp protoc-gen-go /usr/local/bin/2.而后vim ~/.zshrc, 新增上面这一行(貌似我发现这一步能够不要, 执行完第一步当前就能够编译了...)export GOPATH=$HOME/go PATH=$PATH:$GOPATH/bin之后source ~/. zshrc即可

July 20, 2021 · 1 min · jiezi

关于游戏:GolangProtobufPixieJS-开发-Web-多人在线射击游戏原创翻译

简介Superstellar 是一款开源的多人 Web 太空游戏,非常适合入门 Golang 游戏服务器开发。 规定很简略:捣毁挪动的物体,不要被其余玩家和小行星杀死。你领有两种资源 — 生命值(health points)和能量值(energy points)。每次撞击和与小行星的接触都会让你失去生命值。在射击和应用晋升驱动时会耗费能量值。你杀死的对象越多,你的生命值条就会越长。 技术栈游戏分为两个局部:一个地方服务器(central server)和一个在每个客户端的浏览器中运行的前端应用程序(a front end app)。 咱们之所以抉择这个我的项目,次要是因为后端局部。 咱们心愿它是一个能够同时产生许多事件的中央:游戏模仿(game simulation),客户端网络通信(client network communication),统计信息(statistics),监督(monitoring)等等。 所有这些都应该并行高效地运行。因而,Go 以并发为导向的办法和轻量级的形式仿佛是实现此工作的现实工具。 前端局部尽管很重要,但并不是咱们的次要关注点。然而,咱们也发现了一些潜在的乏味问题,如如何利用显卡渲染动画或如何做客户端预测,以使游戏运行安稳和良好。最初咱们决定尝试蕴含:JavaScript, webpack 和 PixieJS 的堆栈。 在本文的其余部分中,我将探讨后端局部,而客户端应用程序将留待当前探讨。 游戏状态主控模仿 - 在一个中央,而且只有一个中央Superstellar 是一款多人游戏,所以咱们须要一个逻辑来决定游戏世界的以后状态及其变动。它应该理解所有客户端的动作,并对产生的事件做出最终决定 — 例如,炮弹是否击中目标或两个物体碰撞的后果是什么。咱们不能让客户端这样做,因为可能会产生两个人对是否有人被枪杀的判断不同。更不用说那些想要破解协定并取得非法劣势的歹意玩家了。因而,存储游戏状态并决定其变动的最佳地位是服务器自身。 上面是服务器工作形式的总体概述。它同时运行三种不同类型的动作: 侦听来自客户端的管制输出运行仿真模仿(simulation)以将状态更新到下一个工夫点向客户端发送以后状态更新 下图显示了飞船的状态和用户输出构造的简化版本。 用户能够随时发送音讯,因而能够批改用户输出构造。仿真步骤每 20 毫秒唤醒一次,并执行两个操作。 首先,它须要用户输出并更新状态(例如,如果用户启用了推力,则减少加速度)。 而后,它获取状态(在 t 时刻)并将其转换为工夫的下一个时刻(t + 1)。 整个过程反复进行。 在 Go 中实现这种并行逻辑非常容易 — 多亏了它的并发个性。每个逻辑都在其本人的 goroutine 中运行,并侦听某些通道(channel),以便从客户端获取数据或同步到 tickers,以定义模仿步骤(simulations steps)的速度或将更新发送回客户端。咱们也不用放心并行性 - Go 会主动利用所有可用的 CPU 内核。goroutine 和通道(channels)的概念很简略,然而功能强大。 与客户端通信服务器通过 websockets 与客户端通信。因为有了 Gorilla web toolkit,在 Golang 应用 websockets 既简略又牢靠。还有一个原生的 websocket 库,然而它的官网文档说它目前短少一些个性,因而举荐应用 Gorilla。 ...

April 9, 2021 · 2 min · jiezi

关于protobuf:mac安装包安装-protoc

1.下载指定版本安装包 https://github.com/protocolbu...,以protobuf-cpp-3.15.6.tar.gz为例。 cd /usr/local/libwget https://github.com/protocolbuffers/protobuf/releases/download/v3.15.6/protobuf-cpp-3.15.6.tar.gz2.下载下来后解压压缩包,并进入目录 tar -xzvf protobuf-cpp-3.15.6.tar.gzcd protobuf-cpp-3.15.63.设置编译目录 ./configure --prefix=/usr/local/protobuf4.装置检测 make check 5.装置及编译 make && make install6.配置环境变量 vim ~/.bash_profile在文件结尾增加环境变量 export PROTOBUF=/usr/local/protobuf export PATH=$PATH:$PROTOBUF/bin应用source命令,使配置文件失效 source ~/.bash_profile7.测试依照后果 protoc --version

March 29, 2021 · 1 min · jiezi

关于protobuf:Protobuf专题四proto开发神器Jprotobuf

0 前言在JAVA团队外部交互数据时,有几种罕用的办法,比方JAVA原生序列化办法、Json序列化办法、Protobuf序列化办法等,比照试验详见:几种序列化形式的性能比拟。 从比照试验可知protobuf性能最佳,因为其高效的序列化性能以及优良的跨平台能力,被广泛应用于跨平台之间的数据交互。但在不须要思考跨平台的数据交互时,protobuf的繁琐流程成了烦人的“污点”。 基于ptoro传输数据的个别流程是,先编写proto文件,再手动编译proto文件生成java类,而后实例化java类并填充数据,最初编译成二进制数据进行传输。其中编写及手动编译proto文件是个较繁琐的过程(比方简单我的项目中proto之间的互相援用),而且由proto生成的java类应用的是创建者模式(即Bulider模式),其实例化后的赋值过程与我的项目历史代码格调不统一,为带来较大的改变。 为解决这个问题,本文将介绍百度团队开发的Jprotobuf工具,并总结应用过程中的注意事项,以及通过试验验证其与谷歌团队开发的Protobuf性能的一致性。 1 Jprotobuf介绍1.1 定义jprotobuf是针对JAVA程序开发一套繁难类库,目标是简化JAVA语言对protobuf类库的应用。应用jprotobuf能够无需再去理解proto文件操作与语法,间接应用JAVA注解定义字段类型即可。 github地址:https://github.com/jhunters/j...1.2 原理jprotobuf工作原理如下: 扫描类上的注解的信息,进行剖析(与protobuf读取proto文件进行剖析过程类似)依据注解剖析的后果,动静生成java代码进行protobuf序列化与反序列化的性能实现应用JDK6及以上的 code compile API进行编译后加载到classloader1.3 性能jprotobuf 次要性能耗费在扫描类上注解,动静生成代码编译的过程。在执行序列化与反序列化的过程中,简直与protobuf生成的代码效率等同。如果应用预编译插件,则无需在运行中进行代码生成与编译,效率更高。 1.4 特点无需编写proto文件及繁琐的手工编译过程,反对基于POJO对象的注解形式,方便快捷。反对protobuf所有类型,包含对象嵌套,数组,枚举类型提供依据proto文件,动静生成代理对象,可省去POJO对象的编写工作。残缺反对proto文件所有性能,包含内联对象,匿名对象,枚举类型提供从POJO对象的注解形式主动生成proto文件的性能, 不便proto形容文件的治理与保护提供预编译Maven插件,进一步晋升运行性能新增预编译gradle插件2.x版本。 反对TimeStamp类型, 与原生protobuf保持一致。 反对Date类型,应用long类型传递 docs2 Jprotobuf装置maven代码如下: <!-- jprotobuf --><dependency> <groupId>com.baidu</groupId> <artifactId>jprotobuf</artifactId> <version>2.4.5</version></dependency><dependency> <groupId>com.baidu</groupId> <artifactId>jprotobuf-precompile-plugin</artifactId> <version>2.2.2</version></dependency>3 案例数据测试思路:通过各自的办法构建雷同的测试数据,进行序列化和反序列比拟两者之间的差别。注意事项:设计的案例数据尽量蕴含所有的数据类型。 3.1 Protobuf数据定义组织构造如图,其中data依赖student,student依赖person,teacher作为第三方扩大包,用于测试扩大性能。 data.proto代码如下: // Google Protocol Buffers Version 3.syntax = "proto3";// Package name.package prcticeProto.messages;// Options for code generation.option java_package = "learnProto.practiceTest.protoModel";option java_outer_classname = "SchoolModel";option java_multiple_files = true;// import packagesimport "google/protobuf/any.proto";import "practiceProto/categories/student.proto";message School { message Location{ string name=1; uint32 id=2; } Location schoolLocation = 1; bool isOpen =2; repeated categories.Student allStudents = 3; google.protobuf.Any extend =4;}student.proto代码如下: ...

February 9, 2021 · 7 min · jiezi

关于protobuf:深入理解-ProtoBuf-原理与工程实践概述

ProtoBuf 作为一种跨平台、语言无关、可扩大的序列化构造数据的办法,已广泛应用于网络数据交换及存储。随着互联网的倒退,零碎的异构性会愈发突出,跨语言的需要会更加显著,同时 gRPC 也大有取代Restful之势,而 ProtoBuf 作为g RPC 跨语言、高性能的法宝,咱们技术人有必要 深刻了解 ProtoBuf 原理,为当前的技术更新和选型打下基础。 我将过来的学习过程以及实践经验,总结成系列文章,与大家一起探讨学习,心愿大家能有所播种,当然其中有不正确的中央也欢送大家批评指正。 本系列文章次要蕴含: 深刻了解 ProtoBuf 原理与工程实际(概述)深刻了解 ProtoBuf 原理与工程实际(编码)深刻了解 ProtoBuf 原理与工程实际(序列化)深刻了解 ProtoBuf 原理与工程实际(工程实际)一、什么是ProtoBufProtoBuf(Protocol Buffers)是一种跨平台、语言无关、可扩大的序列化构造数据的办法,可用于网络数据交换及存储。 在序列化结构化数据的机制中,ProtoBuf是灵便、高效、自动化的,绝对常见的XML、JSON,形容同样的信息,ProtoBuf序列化后数据量更小、序列化/反序列化速度更快、更简略。 一旦定义了要解决的数据的数据结构之后,就能够利用ProtoBuf的代码生成工具生成相干的代码。只需应用 Protobuf 对数据结构进行一次形容,即可利用各种不同语言(proto3反对C++, Java, Python, Go, Ruby, Objective-C, C#)或从各种不同流中对你的结构化数据轻松读写。 二、为什么是 ProtoBuf大家可能会感觉 Google 创造 ProtoBuf 是为了解决序列化速度的,其实实在的起因并不是这样的。 ProtoBuf最先开始是 Google用来解决索引服务器 request/response 协定的。没有ProtoBuf之前,Google 曾经存在了一种 request/response 格局,用于手动解决 request/response 的编解码。它也能反对多版本协定,不过代码不够优雅: if (protocolVersion=1) { doSomething();} else if (protocolVersion=2) { doOtherThing();} ...如果是十分明确的格式化协定,会使新协定变得非常复杂。因为开发人员必须确保申请发起者与解决申请的理论服务器之间的所有服务器都能了解新协定,而后能力切换开关以开始应用新协定。 这也就是每个服务器开发人员都遇到过的低版本兼容、新旧协定兼容相干的问题。 为了解决这些问题,于是ProtoBuf就诞生了。 ProtoBuf 最后被寄托以下 2 个特点: 更容易引入新的字段,并且不须要检查数据的两头服务器能够简略地解析并传递数据,而无需理解所有字段。数据格式更加具备自我描述性,能够用各种语言来解决(C++, Java 等各种语言)。这个版本的 ProtoBuf 仍须要本人手写解析的代码。 ...

February 3, 2021 · 2 min · jiezi

关于protobuf:protobuf优缺点

protobuf长处protobuf的长处: 1、性能好/效率高 工夫开销: XML格式化(序列化)的开销还好;然而XML解析(反序列化)的开销就不敢恭维了。 然而protobuf在这个方面就进行了优化。能够使序列化和反序列化的工夫开销都减短。 空间开销:也缩小了很多 2、有代码生成机制 比方你你写个一下相似构造体的内容 message testA { required int32 m_testA = 1; } 像写一个这样的构造,protobuf能够主动生成它的.h 文件和点.cpp文件。protobuf将对构造体testA的操作封装成一个类。 3、反对向后兼容和向前兼容 当客户端和服务器共事应用一块协定的时候, 当客户端在协定中减少一个字节,并不会影响客户端的应用 4、反对多种编程语言 在Google官网公布的源代码中蕴含了c++、java、Python三种语言 protobuf毛病1、二进制格局导致可读性差 为了进步性能,protobuf采纳了二进制格局进行编码。这间接导致了可读性差。这个间接影响开发测试时候的效率。当然,个别状况下,protobuf十分牢靠,并不会呈现太大的问题。 2、不足自描述 一般来说,XML是自描述的,而protobuf格局则不是。 给你一段二进制格局的协定内容,不配合你写的构造体是看不出来什么作用的。 3、通用性差 protobuf尽管反对了大量语言的序列化和反序列化,但依然并不是一个跨平台和语言的传输规范。在多平台消息传递中,对其余我的项目的兼容性并不是很好,须要做相应的适配革新工作。相比json 和 XML,通用性还是没那么好。

January 31, 2021 · 1 min · jiezi

关于protobuf:golang使用protobuf中的oneof

作用相似c里的联合体,写配置文件相似 message BBB{ string b=1;}message CCC{ int b=1;}message AAA { oneof payload { BBB b; CCC c; }}应用的时候导入例如为xxx模块:创立构造体时候要手动创立oneof里的构造,写法相似: msg := &xxx.AAA{Payload: &xxx.AAA_BBB{B: &xxx.B{"123"}}}解析进去判断是BBB还是CCC的时候应用 switch msg.Payload.(type) { case *(xxx.AAA_BBB): case *(xxx.AAA_CCC):}

January 21, 2021 · 1 min · jiezi

关于protobuf:Protobuf专题二Protobuf的数据类型解析及使用总结

0 前言Protobuf(Protocol Buffer)是Google出品的一种轻量且高效的结构化数据存储格局,性能比Json、XML更强,被广泛应用于数据传输中。然Protobuf中的数据类型泛滥,什么场景利用什么数据类型最正当、最省空间,成为了每个使用者该思考的问题。为了能更充沛的了解和应用Protobuf,本文将聚焦Protobuf的根本数据类型,剖析其不同数据类型的应用场景和注意事项。 留神:在浏览本文之前最好对Protobuf的语法和序列化原理有肯定的理解。举荐文献:【1】序列化:这是一份很有诚意的 Protocol Buffer 语法详解 https://blog.csdn.net/carson_...【2】Protocol Buffer 序列化原理大揭秘 - 为什么Protocol Buffer性能这么好?https://blog.csdn.net/carson_...【3】通过一个残缺例子彻底学会protobuf序列化原理https://cloud.tencent.com/dev...1 根本数据类型的范畴整型范畴 Int8 - [-128 : 127]Int16 - [-32768 : 32767]Int32 - [-2147483648 : 2147483647]Int64 - [-9223372036854775808 : 9223372036854775807]无符号整型范畴 UInt8 - [0 : 255]UInt16 - [0 : 65535]UInt32 - [0 : 4294967295]UInt64 - [0 : 18446744073709551615]浮点数范畴 Float(32bit) = 1bit(符号位)+ 8bits(指数位)+ 23bits(尾数位)指数位的范畴为-2^128 ~ +2^128尾数位的范畴为2^23 = 8388608,一共七位,这意味着最多能有7位有效数字,但相对能保障的为6位,也即float的精度为6~7位有效数字;Double(64bit)= 1bit(符号位)+ 11bits(指数位)+ 52bits(尾数位)指数位的范畴为-2^1024 ~ +2^1024尾数位的范畴为2^52 = 4503599627370496,一共16位,同理,double的精度为15~16位。浮点数的存储形式详见:https://cloud.tencent.com/dev...2 Protobuf的数据类型Protobuf的根本数据类型与JAVA的数据类型映射关系表如下: 映射表来源于Protobuf官网,https://developers.google.com... ...

January 2, 2021 · 3 min · jiezi

关于protobuf:Protobuf专题一基于IDEA实现Proto一站式编辑及编译

0 前言Protobuf作为一种轻量、高效、可扩大的数据存储语言,被广泛应用于数据传输中。目前对于Proto编辑及编译,最传统的办法是先基于文本编辑软件撰写proto文件,再通过Google提供的protoc程序以命令行的模式编译成java类文件,最初再将生成的java类文件移至project的相应地位。传统的办法比拟麻烦,本文将基于IDEA讲述一种一站式编辑及编译的办法。 1 装置1、下载protoc解析器:protobuf-java-3.14.0.zip2、在IDEA中装置插件。包含GenProtobuf和Protocol Buffer Editor,前者用于一键转换proto文件,后者用于编辑proto文件(未装置前,IDEA不反对对proto语法,没有高亮显示和主动补全提醒)。 留神:如果没有在IDEA的插件市场中搜到以上两个插件,可能是IDEA的版本低了,因而须要降级IDEA到2020版。2 配置1、配置Mavenpom.xml文件中增加如下依赖包: <dependencies> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java</artifactId> <version>3.9.1</version> </dependency> <dependency> <groupId>com.google.protobuf</groupId> <artifactId>protobuf-java-util</artifactId> <version>3.9.1</version> </dependency></dependencies>2、配置GenProtobufIDEA菜单栏 --> Tools --> Configure GenProtobuf --> 配置地址 protoc path是下载的protoc.exe的地址。本文的目标是将proto生成java类,因而生成对象选为java,并填写相应地址。3 应用1、编辑proto装置好Protocol Buffer Editor插件后,无需配置即可应用,由图可见,proto语法的关键字高亮显示了,便于编辑。 2、编译proto右键proto文件,能够看见两个跟proto无关的选项,一个是“quick gen protobuf here”,另一个是“quick gen protobuf rules”。前者示意在proto所在的文件门路下生成java文件,后者示意按配置的地址生成java文件。 前文曾经配置好了java生成的门路,咱们抉择后者生成即可,成果如图:其中,MyTest即为test.proto生成的java类。 4 测试生成了java类文件后,咱们就能够测试其序列化和反序列化性能,如果能够失常运行并解析正确,阐明咱们的操作无误。 测试代码: package learnProto.selfTest;import com.google.protobuf.InvalidProtocolBufferException;import learnProto.selfTest.MyTest.*;import java.util.Arrays;public class Test { public static void main(String[] args) { convertProto(1); } public static void convertProto(int value) { //1.通过build创立音讯结构器 Data.Builder dataBuilder = Data.newBuilder(); //2.设置字段值 dataBuilder.setInt32(value); //3.通过音讯结构器结构音讯对象 Data data = dataBuilder.build(); //4.序列化 byte[] bytes = data.toByteArray(); System.out.println(value+"序列化后的数据:" + Arrays.toString(bytes)+",字节个数:"+bytes.length); //5.反序列化 try { Data parseFrom = Data.parseFrom(bytes); System.out.println("还原后的数据="+parseFrom.getInt32()); } catch (InvalidProtocolBufferException e) { e.printStackTrace(); } }}运行后果: ...

January 2, 2021 · 1 min · jiezi

关于protobuf:区分Protobuf-3中缺失值和默认值

来自公众号:新世界杂货铺这两天翻了翻以前的我的项目,发现不同我的项目中对于Protobuf 3缺失值和默认值的辨别竟然有好几种实现。明天笔者冷饭新炒,联合我的项目中的实现以及切身经验共总结出如下六种计划。 减少标识字段家喻户晓,在Go中数字类型的默认值为0(这里仅以数字类型举例),这在某些场景下往往会引起肯定的歧义。 以is_show字段为例,如果没有该字段示意不更新DB中的数据,如果有该字段且值为0则示意更新DB中的数据为不可见,如果有该字段且值为1则示意更新DB中的数据为可见。上述场景中,理论要解决的问题是如何辨别默认值和缺失字段。减少标识字段是通过额定减少一个字段来达到辨别的目标。 例如:减少一个has_show_field字段标识is_show是否为有效值。如果has_show_field为true则is_show为有效值,否则认为is_show未设置值。 此计划尽管直白,但每次设置is_show的值时还需设置has_show_field的值,甚是麻烦故笔者非常不举荐。 字段含意和默认值辨别字段含意和默认值辨别即不应用对应类型的默认值作为该字段的有效值。接着后面的例子持续形容,is_show为1时示意展现,is_show为2时示意不展现,其余状况则认为is_show未设置值。 此计划笔者还是比拟认可的,惟一问题就是和开发者的默认习惯稍微不符。 应用oneofoneof 的用意是达到 C 语言 union 数据类型的成果,然而诸多大佬还是发现它能够标识缺失字段。 message Status { oneof show { int32 is_show = 1; }}message Test { int32 bar = 1; Status st = 2;}上述proto文件生成对应go文件后,Test.St为Status的指针类型,故通过此计划能够辨别默认值和缺失字段。然而笔者认为此计划做json序列化时非常不敌对,上面是笔者的例子: // oneof to jsonot1 := oneof.Test{ Bar: 1, St: &oneof.Status{ Show: &oneof.Status_IsShow{ IsShow: 1, }, },}bts, err := json.Marshal(ot1)fmt.Println(string(bts), err)// json to oneof failedjsonStr := `{"bar":1,"st":{"Show":{"is_show":1}}}`var ot2 oneof.Testfmt.Println(json.Unmarshal([]byte(jsonStr), &ot2))上述输入后果如下: {"bar":1,"st":{"Show":{"is_show":1}}} <nil>json: cannot unmarshal object into Go struct field Status.st.Show of type oneof.isStatus_Show通过上述输入知,oneof的json.Marshal输入后果会额定多一层,而json.Unmarshal还会失败,因而应用oneof时需谨慎。 ...

December 11, 2020 · 2 min · jiezi

关于protobuf:关于头条人群包protobuf格式的phppython解决方案

继上文: PHP应用protobuf 尽管php能序列化和反序列化,然而奈何头条不认啊,最初应用了python脚本的模式,去序列化,但很快就暴露出了问题,速度太慢!几万个设施号要序列化2小时+,当然次要的起因在于过后赶时间,是一个个设施号序列化的,大量的工夫花在python上下文切换上,上文里的脚本能用,然而不适宜略微量大一点的场景,故而用三脚猫的功夫写了一个新的python脚本,承受文件,吐出序列化后的新文件,速度大大晋升,实测大略1000/s个设施号。 from __future__ import print_functionimport DmpDataProtoV2_pb2import os,sysimport timeimport base64ag_len = sys.argv.__len__()if ag_len <= 1: print ('ag is null') exit()file = sys.argv[1]if not file.strip(): print ('files is null') exit()if not os.path.exists(file): print ('files is not exists') exit()f = open(file)line = f.readline()line=line.strip('\n')base_name = os.path.splitext(file)[0]target_file = base_name + '-ProtoBuf.txt'print(target_file)# if os.path.exists(target_file)::# os.remove(target_file)t = open(target_file, 'w')t.truncate()while line: line=line.strip('\n') if not line.strip(): continue arr = line.split('|') if arr.__len__() != 2: continue dmp_data = DmpDataProtoV2_pb2.DmpData() id_item1 = dmp_data.idList.add() dtype = arr[0] dev_id = arr[1] id_item1.dataType = getattr(DmpDataProtoV2_pb2.IdItem,dtype) #id_item1.dataType = DmpDataProtoV2_pb2.IdItem.IDFA id_item1.id = str.lower(dev_id) id_item1.tags.append(dtype) # id_item1.timestamp = int(time.time()) binary_string = dmp_data.SerializeToString() s = base64.b64encode(binary_string) t.write(s+"\n"); line = f.readline() line=line.strip('\n')f.close()PHP调用局部 ...

November 27, 2020 · 1 min · jiezi

关于protobuf:proto语法说明

官网文档:https://developers.google.cn/... 一、根本语法示例/* 头部相干申明*/syntax = "proto3"; // 语法版本为protobuf3.0package = "com.xxx.foo"; // 定义包名import "common.proto"; // 导入common.protooption java_package = "com.xxx.foo"; // 指定java包// 搜寻申请message SearchRequest{ int32 page = 1; // 当前页 int32 page_size = 2; // 一页多少条,应用下划线分隔,设置的时候应用驼峰命令法,如:setPageSize(10); enum Type { IN = 0; // 0须要是第一个,第一个也是默认值 OUT = 1; } Type type = 3; // 类型}// 搜寻响应message SearchResponse{ int32 code = 1; // 状态码 string message = 2; // 音讯 SearchList data = 3; // 数据,类型为SearchList}// SearchList构造message SearchList{ repeated Item data = 1; // 数据记录项,repeated类型用来寄存N个雷同类型的内容 int64 count = 2; // 总条数 int32 page_size = 3; // 一页条数}// Item构造message Item{ int64 id = 1; // id string title = 2; // 题目 int64 create_time = 3; // 创立工夫 int64 update_time = 4; // 更新工夫}// 服务service SearchService{ rpc GetSearchList(SearchRequest) returns (SearchResponse); // rpc 办法}命令标准倡议应用下面示例字段类型有: ...

September 25, 2020 · 1 min · jiezi

用Golang构建gRPC服务

本教程提供了Go使用gRPC的基础教程 在教程中你将会学到如何: 在.proto文件中定义一个服务。使用protocol buffer编译器生成客户端和服务端代码。使用gRPC的Go API为你的服务写一个客户端和服务器。继续之前,请确保你已经对gRPC概念有所了解,并且熟悉protocol buffer。需要注意的是教程中的示例使用的是proto3版本的protocol buffer:你可以在Protobuf语言指南与Protobuf生成Go代码指南中了解到更多相关知识。 为什么使用gRPC我们的示例是一个简单的路线图应用,客户端可以获取路线特征信息、创建他们的路线摘要,还可以与服务器或者其他客户端交换比如交通状态更新这样的路线信息。 借助gRPC,我们可以在.proto文件中定义我们的服务,并以gRPC支持的任何语言来实现客户端和服务器,客户端和服务器又可以在从服务器到你自己的平板电脑的各种环境中运行-gRPC还会为你解决所有不同语言和环境之间通信的复杂性。我们还获得了使用protocol buffer的所有优点,包括有效的序列化(速度和体积两方面都比JSON更有效率),简单的IDL(接口定义语言)和轻松的接口更新。 安装安装grpc包首先需要安装gRPC golang版本的软件包,同时官方软件包的examples目录里就包含了教程中示例路线图应用的代码。 $ go get google.golang.org/grpc然后切换到`grpc-go/examples/route_guide:`目录: $ cd $GOPATH/src/google.golang.org/grpc/examples/route_guide安装相关工具和插件安装protocol buffer编译器安装编译器最简单的方式是去https://github.com/protocolbu... 下载预编译好的protoc二进制文件,仓库中可以找到每个平台对应的编译器二进制文件。这里我们以Mac Os为例,从https://github.com/protocolbu... 下载并解压文件。 更新PATH系统变量,或者确保protoc放在了PATH包含的目录中了。 安装protoc编译器插件$ go get -u github.com/golang/protobuf/protoc-gen-go编译器插件protoc-gen-go将安装在$GOBIN中,默认位于$GOPATH/bin。编译器protoc必须在$PATH中能找到它: $ export PATH=$PATH:$GOPATH/bin定义服务首先第一步是使用protocol buffer定义gRPC服务还有方法的请求和响应类型,你可以在下载的示例代码examples/route_guide/routeguide/route_guide.proto中看到完整的.proto文件。 要定义服务,你需要在.proto文件中指定一个具名的service service RouteGuide { ...}然后在服务定义中再来定义rpc方法,指定他们的请求和响应类型。gRPC允许定义四种类型的服务方法,这四种服务方法都会应用到我们的RouteGuide服务中。 一个简单的RPC,客户端使用存根将请求发送到服务器,然后等待响应返回,就像普通的函数调用一样。// 获得给定位置的特征rpc GetFeature(Point) returns (Feature) {}服务器端流式RPC,客户端向服务器发送请求,并获取流以读取回一系列消息。客户端从返回的流中读取,直到没有更多消息为止。如我们的示例所示,可以通过将<font color="red">stream</font>关键字放在响应类型之前来指定服务器端流方法。//获得给定Rectangle中可用的特征。结果是//流式传输而不是立即返回//因为矩形可能会覆盖较大的区域并包含大量特征。rpc ListFeatures(Rectangle) returns (stream Feature) {}客户端流式RPC,其中客户端使用gRPC提供的流写入一系列消息并将其发送到服务器。客户端写完消息后,它将等待服务器读取所有消息并返回其响应。通过将<font color="red">stream</font>关键字放在请求类型之前,可以指定客户端流方法。// 接收路线上被穿过的一系列点位, 当行程结束时// 服务端会返回一个RouteSummary类型的消息.rpc RecordRoute(stream Point) returns (RouteSummary) {}双向流式RPC,双方都使用读写流发送一系列消息。这两个流是独立运行的,因此客户端和服务器可以按照自己喜欢的顺序进行读写:例如,服务器可以在写响应之前等待接收所有客户端消息,或者可以先读取消息再写入消息,或其他一些读写组合。每个流中的消息顺序都会保留。您可以通过在请求和响应之前都放置<font color="red">stream</font>关键字来指定这种类型的方法。//接收路线行进中发送过来的一系列RouteNotes类型的消息,同时也接收其他RouteNotes(例如:来自其他用户)rpc RouteChat(stream RouteNote) returns (stream RouteNote) {}我们的.proto文件中也需要所有请求和响应类型的protocol buffer消息类型定义。比如说下面的Point消息类型: // Points被表示为E7表示形式中的经度-纬度对。//(度数乘以10 ** 7并四舍五入为最接近的整数)。// 纬度应在+/- 90度范围内,而经度应在// 范围+/- 180度(含)message Point { int32 latitude = 1; int32 longitude = 2;}生成客户端和服务端代码接下来要从我们的.proto服务定义生成gRPC客户端和服务端的接口。我们使用protoc编译器和上面安装的编译器插件来完成这些工作: ...

October 6, 2019 · 4 min · jiezi

在Golang中使用Protobuf

本教程使用proto3版本的protocol buffer语言,提供了一个基本的在Go程序中使用protocol buffer的介绍。通过创建一个简单的示例应用程序,向你展示如何 在.proto文件中定义消息格式。使用protoc编译器编译生成Go代码。使用Go的protocol buffer API读写消息。它不是一个全面的在Go中使用protocol buffer的指南,更详细的参考信息请查看前面的两个教程。 为什么使用protocol buffer我们将要使用的示例是一个非常简单的“地址簿”应用程序,可以在文件中读取和写入人员的联系人详细信息。地址簿中的每个人都有姓名,ID,电子邮件地址和联系电话号码。 如何序列化和检索这样的结构化数据?有几种方法可以解决这个问题: 使用gobs(Go中自定义的序列化编码格式)序列化Go数据结构。这是Go特定环境中的一个很好的解决方案,但如果需要与为其他平台编写的应用程序共享数据,它将无法正常工作。可以发明一种特殊的方法将数据项编码为单个字符串 - 例如将4个整数编码为“12:3:-23:67”。这是一种简单而灵活的方法,虽然它确实需要编写一次性编码和解析代码,并且解析会产生较小的运行时成本。这最适合编码非常简单的数据。将数据序列化为XML。这种方法非常有吸引力,因为XML(有点)是人类可读懂的,并且有许多语言都有相应的类库。如果您想与其他应用程序/项目共享数据,这可能是一个不错的选择。然而,XML是众所周知的空间密集型,并且编码/解码它会对应用程序造成巨大的性能损失。此外,导航XML DOM树比通常在类中导航简单字段要复杂得多。protocol buffer是灵活,高效,自动化的解决方案,可以解决这个问题。使用protocol buffer,您可以编写要存储的数据结构的.proto描述。由此,protocol buffer编译器会创建一个类,该类使用有效的二进制格式实现协议缓冲区数据的自动编码和解析。生成的类会为构成protocol buffer的字段提供getter和setter,并负责将protocol buffer作为一个单元读取和写入的细节。重要的是,protocol buffer格式支持随着时间的推移扩展格式的想法,使得代码仍然可以读取使用旧格式编码的数据。 获得示例程序示例是一组用于管理地址簿数据文件的命令行应用程序,使用protocol buffer进行编码。命令add_person_go向数据文件添加新条目。命令list_people_go解析数据文件并将数据打印到控制台。 下载这些文件到你的项目目录中: 描述protocol buffer消息格式的.proto文件 addressbook.proto命令行程序add_person.go,list_people.go定义协议格式要创建地址簿应用程序,您需要从.proto文件开始。 .proto文件中的定义很简单:为要序列化的每个数据结构定义消息,然后为消息中的每个字段指定名称和类型。在我们的示例中,定义消息的.proto文件是addressbook.proto。 .proto文件以包声明开头,这有助于防止不同项目之间的命名冲突。 syntax = "proto3";package tutorial;import "google/protobuf/timestamp.proto";在Go中,protocol buffer的包名称用作Go包,除非您指定了go_package。即使你确实提供了go_package,你仍然应该在.proto文件中定义一个包名,以避免在Protocol Buffers命名空间和非Go语言中发生名称冲突。 接下来,是消息定义。消息只是包含一组类型字段的聚合。许多标准的简单数据类型都可用作字段类型,包括bool,int32,float,double和string。您还可以使用其他消息类型作为字段类型,为消息添加更多结构。 message Person { string name = 1; int32 id = 2; // Unique ID number for this person. string email = 3; enum PhoneType { MOBILE = 0; HOME = 1; WORK = 2; } message PhoneNumber { string number = 1; PhoneType type = 2; } repeated PhoneNumber phones = 4; google.protobuf.Timestamp last_updated = 5;}// Our address book file is just one of these.message AddressBook { repeated Person people = 1;}在上面的示例中,Person消息包含PhoneNumber消息,而AddressBook消息包含Person消息。您甚至可以定义嵌套在其他消息中的消息类型 - 如您所见,PhoneNumber类型在Person中定义。如果您希望其中一个字段值的取值范围是预定义的值列表中的值,还可以定义枚举类型 - 此处你要指定电话号码可以是MOBILE,HOME或WORK之一。 ...

September 20, 2019 · 2 min · jiezi

Go内存分配那些事就这么简单

原文链接:https://mp.weixin.qq.com/s/3g...新老朋友好久不见,我是大彬,这篇文章准备了很久,不是在拖延,而是中间做了一些其他事情,耽搁了一些。 这篇文章主要介绍Go内存分配和Go内存管理,会轻微涉及内存申请和释放,以及Go垃圾回收。 从非常宏观的角度看,Go的内存管理就是下图这个样子,我们今天主要关注其中标红的部分。 友情提醒:文章有点长,建议先收藏,后阅读,绝对是学习内存管理的好资料。 本文基于go1.11.2,不同版本Go的内存管理可能存在差别,比如1.9与1.11的mheap定义就是差别比较大的,后续看源码的时候,请注意你的go版本,但无论你用哪个go版本,这都是一个优秀的资料,因为内存管理的思想和框架始终未变。 Go这门语言抛弃了C/C++中的开发者管理内存的方式:主动申请与主动释放,增加了逃逸分析和GC,将开发者从内存管理中释放出来,让开发者有更多的精力去关注软件设计,而不是底层的内存问题。这是Go语言成为高生产力语言的原因之一。 我们不需要精通内存的管理,因为它确实很复杂,但掌握内存的管理,可以让你写出更高质量的代码,另外,还能助你定位Bug。 这篇文章采用层层递进的方式,依次会介绍关于存储的基本知识,Go内存管理的“前辈”TCMalloc,然后是Go的内存管理和分配,最后是总结。这么做的目的是,希望各位能通过全局的认识和思考,拥有更好的编码思维和架构思维。 最后,这不是一篇源码分析文章,因为Go源码分析的文章已经有很多了,这些源码文章能够帮助你去学习具体的工程实践和奇淫巧计了,文章的末尾会推荐一些优秀文章,如果你对内存感兴趣,建议每一篇都去看一下,挑出自己喜欢的,多花时间研究下。 1. 存储基础知识回顾这部分我们简单回顾一下计算机存储体系、虚拟内存、栈和堆,以及堆内存的管理,这部分内容对理解和掌握Go内存管理比较重要,建议忘记或不熟悉的朋友不要跳过。 存储金字塔 这幅图表达了计算机的存储体系,从上至下依次是: CPU寄存器Cache内存硬盘等辅助存储设备鼠标等外接设备从上至下,访问速度越来越慢,访问时间越来越长。 你有没有思考过下面2个简单的问题,如果没有不妨想想: 如果CPU直接访问硬盘,CPU能充分利用吗?如果CPU直接访问内存,CPU能充分利用吗?CPU速度很快,但硬盘等持久存储很慢,如果CPU直接访问磁盘,磁盘可以拉低CPU的速度,机器整体性能就会低下,为了弥补这2个硬件之间的速率差异,所以在CPU和磁盘之间增加了比磁盘快很多的内存。 然而,CPU跟内存的速率也不是相同的,从上图可以看到,CPU的速率提高的很快(摩尔定律),然而内存速率增长的很慢,虽然CPU的速率现在增加的很慢了,但是内存的速率也没增加多少,速率差距很大,从1980年开始CPU和内存速率差距在不断拉大,为了弥补这2个硬件之间的速率差异,所以在CPU跟内存之间增加了比内存更快的Cache,Cache是内存数据的缓存,可以降低CPU访问内存的时间。 不要以为有了Cache就万事大吉了,CPU的速率还在不断增大,Cache也在不断改变,从最初的1级,到后来的2级,到当代的3级Cache,(有兴趣看cache历史)。 三级Cache分别是L1、L2、L3,它们的速率是三个不同的层级,L1速率最快,与CPU速率最接近,是RAM速率的100倍,L2速率就降到了RAM的25倍,L3的速率更靠近RAM的速率。 看到这了,你有没有Get到整个存储体系的分层设计?自顶向下,速率越来越低,访问时间越来越长,从磁盘到CPU寄存器,上一层都可以看做是下一层的缓存。 看了分层设计,我们看一下内存,毕竟我们是介绍内存管理的文章。 虚拟内存虚拟内存是当代操作系统必备的一项重要功能了,它向进程屏蔽了底层了RAM和磁盘,并向进程提供了远超物理内存大小的内存空间。我们看一下虚拟内存的分层设计。 上图展示了某进程访问数据,当Cache没有命中的时候,访问虚拟内存获取数据的过程。 访问内存,实际访问的是虚拟内存,虚拟内存通过页表查看,当前要访问的虚拟内存地址,是否已经加载到了物理内存,如果已经在物理内存,则取物理内存数据,如果没有对应的物理内存,则从磁盘加载数据到物理内存,并把物理内存地址和虚拟内存地址更新到页表。 有没有Get到:物理内存就是磁盘存储缓存层。 另外,在没有虚拟内存的时代,物理内存对所有进程是共享的,多进程同时访问同一个物理内存存在并发访问问题。引入虚拟内存后,每个进程都要各自的虚拟内存,内存的并发访问问题的粒度从多进程级别,可以降低到多线程级别。 栈和堆我们现在从虚拟内存,再进一层,看虚拟内存中的栈和堆,也就是进程对内存的管理。 上图展示了一个进程的虚拟内存划分,代码中使用的内存地址都是虚拟内存地址,而不是实际的物理内存地址。栈和堆只是虚拟内存上2块不同功能的内存区域: 栈在高地址,从高地址向低地址增长。堆在低地址,从低地址向高地址增长。栈和堆相比有这么几个好处: 栈的内存管理简单,分配比堆上快。栈的内存不需要回收,而堆需要,无论是主动free,还是被动的垃圾回收,这都需要花费额外的CPU。栈上的内存有更好的局部性,堆上内存访问就不那么友好了,CPU访问的2块数据可能在不同的页上,CPU访问数据的时间可能就上去了。堆内存管理 我们再进一层,当我们说内存管理的时候,主要是指堆内存的管理,因为栈的内存管理不需要程序去操心。这小节看下堆内存管理干的是啥,如上图所示主要是3部分:分配内存块,回收内存块和组织内存块。 在一个最简单的内存管理中,堆内存最初会是一个完整的大块,即未分配内存,当来申请的时候,就会从未分配内存,分割出一个小内存块(block),然后用链表把所有内存块连接起来。需要一些信息描述每个内存块的基本信息,比如大小(size)、是否使用中(used)和下一个内存块的地址(next),内存块实际数据存储在data中。 一个内存块包含了3类信息,如下图所示,元数据、用户数据和对齐字段,内存对齐是为了提高访问效率。下图申请5Byte内存的时候,就需要进行内存对齐。 释放内存实质是把使用的内存块从链表中取出来,然后标记为未使用,当分配内存块的时候,可以从未使用内存块中有先查找大小相近的内存块,如果找不到,再从未分配的内存中分配内存。 上面这个简单的设计中还没考虑内存碎片的问题,因为随着内存不断的申请和释放,内存上会存在大量的碎片,降低内存的使用率。为了解决内存碎片,可以将2个连续的未使用的内存块合并,减少碎片。 以上就是内存管理的基本思路,关于基本的内存管理,想了解更多,可以阅读这篇文章《Writing a Memory Allocator》,本节的3张图片也是来自这片文章。 2. TCMallocTCMalloc是Thread Cache Malloc的简称,是Go内存管理的起源,Go的内存管理是借鉴了TCMalloc,随着Go的迭代,Go的内存管理与TCMalloc不一致地方在不断扩大,但其主要思想、原理和概念都是和TCMalloc一致的,如果跳过TCMalloc直接去看Go的内存管理,也许你会似懂非懂。 掌握TCMalloc的理念,无需去关注过多的源码细节,就可以为掌握Go的内存管理打好基础,基础打好了,后面知识才扎实。 在Linux里,其实有不少的内存管理库,比如glibc的ptmalloc,FreeBSD的jemalloc,Google的tcmalloc等等,为何会出现这么多的内存管理库?本质都是在多线程编程下,追求更高内存管理效率:更快的分配是主要目的。 那如何更快的分配内存? 我们前面提到: 引入虚拟内存后,让内存的并发访问问题的粒度从多进程级别,降低到多线程级别。这是更快分配内存的第一个层次。 同一进程的所有线程共享相同的内存空间,他们申请内存时需要加锁,如果不加锁就存在同一块内存被2个线程同时访问的问题。 TCMalloc的做法是什么呢?为每个线程预分配一块缓存,线程申请小内存时,可以从缓存分配内存,这样有2个好处: 为线程预分配缓存需要进行1次系统调用,后续线程申请小内存时,从缓存分配,都是在用户态执行,没有系统调用,缩短了内存总体的分配和释放时间,这是快速分配内存的第二个层次。多个线程同时申请小内存时,从各自的缓存分配,访问的是不同的地址空间,无需加锁,把内存并发访问的粒度进一步降低了,这是快速分配内存的第三个层次。基本原理下面就简单介绍下TCMalloc,细致程度够我们理解Go的内存管理即可。 声明:我没有研究过TCMalloc,以下介绍根据TCMalloc官方资料和其他博主资料总结而来,错误之处请朋友告知我。 结合上图,介绍TCMalloc的几个重要概念: Page:操作系统对内存管理以页为单位,TCMalloc也是这样,只不过TCMalloc里的Page大小与操作系统里的大小并不一定相等,而是倍数关系。《TCMalloc解密》里称x64下Page大小是8KB。Span:一组连续的Page被称为Span,比如可以有2个页大小的Span,也可以有16页大小的Span,Span比Page高一个层级,是为了方便管理一定大小的内存区域,Span是TCMalloc中内存管理的基本单位。ThreadCache:每个线程各自的Cache,一个Cache包含多个空闲内存块链表,每个链表连接的都是内存块,同一个链表上内存块的大小是相同的,也可以说按内存块大小,给内存块分了个类,这样可以根据申请的内存大小,快速从合适的链表选择空闲内存块。由于每个线程有自己的ThreadCache,所以ThreadCache访问是无锁的。CentralCache:是所有线程共享的缓存,也是保存的空闲内存块链表,链表的数量与ThreadCache中链表数量相同,当ThreadCache内存块不足时,可以从CentralCache取,当ThreadCache内存块多时,可以放回CentralCache。由于CentralCache是共享的,所以它的访问是要加锁的。PageHeap:PageHeap是堆内存的抽象,PageHeap存的也是若干链表,链表保存的是Span,当CentralCache没有内存的时,会从PageHeap取,把1个Span拆成若干内存块,添加到对应大小的链表中,当CentralCache内存多的时候,会放回PageHeap。如下图,分别是1页Page的Span链表,2页Page的Span链表等,最后是large span set,这个是用来保存中大对象的。毫无疑问,PageHeap也是要加锁的。 上文提到了小、中、大对象,Go内存管理中也有类似的概念,我们瞄一眼TCMalloc的定义: 小对象大小:0~256KB中对象大小:257~1MB大对象大小:>1MB小对象的分配流程:ThreadCache -> CentralCache -> HeapPage,大部分时候,ThreadCache缓存都是足够的,不需要去访问CentralCache和HeapPage,无锁分配加无系统调用,分配效率是非常高的。 ...

September 9, 2019 · 2 min · jiezi

Go是如何实现protobuf的编解码的2源码

原文链接:https://mp.weixin.qq.com/s/oY...这是一篇姊妹篇文章,浅析一下Go是如何实现protobuf编解码的: Go是如何实现protobuf的编解码的(1): 原理Go是如何实现protobuf的编解码的(2): 源码本编是第二篇。 前言上一篇文章Go是如何实现protobuf的编解码的(1):原理中已经指出了Go语言数据和Protobuf数据的编解码是由包github.com/golang/protobuf/proto完成的,本编就来分析一下proto包是如何实现编解码的。 编解码原理编解码包都有支持的编解码类型,我们暂且把这些类型称为底层类型,编解码的本质是: 为每一个底层类型配备一个或多个编解码函数把一个结构体的字段,递归的拆解成底层类型,然后选择合适的函数进行编码或解码操作 接下来先看编码,再看解码。 编码约定:以下所有的代码片,如果是request.pb.go或main.go中的代码,会在第一行标记文件名,否则都是proto包的源码。// main.gopackage mainimport ( "fmt" "./types" "github.com/golang/protobuf/proto")func main() { req := &types.Request{Data: "Hello Dabin"} // Marshal encoded, err := proto.Marshal(req) if err != nil { fmt.Printf("Encode to protobuf data error: %v", err) } ...}编码调用的是proto.Marshal函数,它可以完成的是Go语言数据序列化成protobuf数据,返回序列化结果或错误。 proto编译成的Go结构体都是符合Message接口的,从Marshal可知Go结构体有3种序列化方式: pb Message满足newMarshaler接口,则调用XXX_Marshal()进行序列化。pb满足Marshaler接口,则调用Marshal()进行序列化,这种方式适合某类型自定义序列化规则的情况。否则,使用默认的序列化方式,创建一个Warpper,利用wrapper对pb进行序列化,后面会介绍方式1实际就是使用方式3。// Marshal takes a protocol buffer message// and encodes it into the wire format, returning the data.// This is the main entry point.func Marshal(pb Message) ([]byte, error) { if m, ok := pb.(newMarshaler); ok { siz := m.XXX_Size() b := make([]byte, 0, siz) return m.XXX_Marshal(b, false) } if m, ok := pb.(Marshaler); ok { // If the message can marshal itself, let it do it, for compatibility. // NOTE: This is not efficient. return m.Marshal() } // in case somehow we didn't generate the wrapper if pb == nil { return nil, ErrNil } var info InternalMessageInfo siz := info.Size(pb) b := make([]byte, 0, siz) return info.Marshal(b, pb, false)}newMarshaler和Marshaler如下: ...

September 9, 2019 · 13 min · jiezi

Go是如何实现protobuf的编解码的1原理

原文链接:https://mp.weixin.qq.com/s/O8...这是一篇姊妹篇文章,浅析一下Go是如何实现protobuf编解码的: Go是如何实现protobuf的编解码的(1): 原理Go是如何实现protobuf的编解码的(2): 源码本编是第一篇。 Protocol Buffers介绍Protocol buffers缩写为protobuf,是由Google创造的一种用于序列化的标记语言,项目Github仓库:https://github.com/protocolbu...。 Protobuf主要用于不同的编程语言的协作RPC场景下,定义需要序列化的数据格式。Protobuf本质上仅仅是一种用于交互的结构式定义,从功能上和XML、JSON等各种其他的交互形式都并无本质不同,只负责定义不负责数据编解码。 其官方介绍如下: Protocol buffers are Google's language-neutral, platform-neutral, extensible mechanism for serializing structured data – think XML, but smaller, faster, and simpler. You define how you want your data to be structured once, then you can use special generated source code to easily write and read your structured data to and from a variety of data streams and using a variety of languages.Protocol buffers的多语言支持protobuf是支持多种编程语言的,即多种编程语言的类型数据可以转换成protobuf定义的类型数据,各种语言的类型对应可以看此介绍。 ...

September 9, 2019 · 3 min · jiezi

Protobuf-小试牛刀

本文以PHP为例。环境: CentOS 6.8proto 3.8PHP 7.1.12PHP protobuf扩展 3.8.0go1.12.5 linux/amd64本文示例仓库地址: https://github.com/52fhy/prot... 是什么Protobuf是一种平台无关、语言无关、可扩展且轻便高效的序列化数据结构的协议,可以用于网络通信和数据存储。 官方文档:https://github.com/protocolbu... 作为数据交换协议,常见的还有JSON、XML。相比JSON,Protobuf有更高的转化效率。一般JSON用于HTTP接口,Protobuf用于RPC比较多。以gRPC为例,默认就是使用Protobuf。 我们可以使用Protobuf: 作为RPC的序列化数据结构的协议。类似于JSON定义proto文件,一键生成多语言代码。安装安装清单一览: protoc各编程语言对应的protobuf库安装protoc为了将proto文件转成编程语言代码,需要安装编译工具。 地址:https://github.com/protocolbu... wget https://github.com/protocolbuffers/protobuf/releases/download/v3.8.0/protoc-3.8.0-linux-x86_64.zipunzip protoc-3.8.0-linux-x86_64.zipcp bin/protoc /usr/bin/cp -r include/google /usr/include/注:最后一行是为了将proto的一些库复制到系统,例如google/protobuf/any.proto,如果不复制,编译如果用了里面的库例如Any,会提示:protobuf google.protobuf.Any not found 。mac版地址: https://github.com/protocolbu... windows版地址: https://github.com/protocolbu... 然后命令行输入 protoc可以查看帮助。 假设有一个 .proto格式的文件,需要编译成其它语言代码,以PHP为例则是: mkdir phpprotoc --php_out=php *.proto其中--php_out=php表示编译成PHP代码,放在php目录。protof还支持: $ protoc | grep "=OUT_DIR" --cpp_out=OUT_DIR Generate C++ header and source. --csharp_out=OUT_DIR Generate C# source file. --java_out=OUT_DIR Generate Java source file. --js_out=OUT_DIR Generate JavaScript source. --objc_out=OUT_DIR Generate Objective C header and source. --php_out=OUT_DIR Generate PHP source file. --python_out=OUT_DIR Generate Python source file. --ruby_out=OUT_DIR Generate Ruby source file.后面有示例说明。 ...

July 7, 2019 · 3 min · jiezi

php中使用protobuffer

Protobuf 简介protobuf(Protocol buffers)是谷歌出品的跨平台、跨语言、可扩展的数据传输及存储的协议,是高效的数据压缩编码方式之一。 Protocol buffers 在序列化数据方面,它是灵活的,高效的。相比于 XML 来说,Protocol buffers 更加小巧,更加快速,更加简单。一旦定义了要处理的数据的数据结构之后,就可以利用 Protocol buffers 的代码生成工具生成相关的代码。甚至可以在无需重新部署程序的情况下更新数据结构。只需使用 Protobuf 对数据结构进行一次描述,即可利用各种不同语言或从各种不同数据流中对你的结构化数据轻松读写。 Protocol buffers 很适合做数据存储或 RPC 数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。 此外,Protobuf由于其在内网高效的数据交换效率,是被广泛应用于微服务的,在谷歌的开源框架grpc即是基于此构建起来的。 php-protobuf安装由于protobuf原生并不支持php,所以php如果使用pb则需要安装相应扩展。 pecl install protobuf环境中需要有protoc编译器,下载安装方式: $ wget https://github.com/google/protobuf/releases/download/v2.5.0/protobuf-2.5.0.tar.gz$ tar zxvf protobuf-2.5.0.tar.gz$ cd protobuf-2.5.0$ ./configure --prefix=/usr/local/protobuf$ sudo make $ sudo make install验证安装成功: $ /usr/local/protobuf/bin/protoc --versionlibprotoc 2.5.0php-protobuf安装成功 php --ri protobuf安装lumen和google/protobuf依赖lumen new rpclumen new rpc命令相当于composer create-project laravel/lumen rpccomposer require google/protobuf在composer.json下添加classmap: { "classmap": [ "protobuf/" ]}ok,准备工作都已做好了。 自己做一个demo在代码目录下创建一个protobuf文件夹mkdir protobuf 进入该目录,创建一个文件searchRequest.proto syntax = "proto3";message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3; enum Corpus { UNIVERSAL = 0; WEB = 1; IMAGES = 2; LOCAL = 3; NEWS = 4; PRODUCTS = 5; VIDEO = 6; } Corpus corpus = 4;}????此处很重要????在composer.json下添加classmap,否则将无法侦测到对应class{ "classmap": [ "protobuf/" ]}在命令行下运行:protoc --proto_path=protobuf/ --php_out=protobuf/ protobuf/searchRequest.proto && composer dump-autoload ...

June 18, 2019 · 2 min · jiezi

netty 基于 protobuf 协议 实现 websocket 版本的简易客服系统

结构netty 作为服务端protobuf 作为序列化数据的协议websocket 前端通讯演示netty 服务端实现Server.java 启动类import io.netty.bootstrap.ServerBootstrap;import io.netty.channel.ChannelFuture;import io.netty.channel.EventLoopGroup;import io.netty.channel.nio.NioEventLoopGroup;import io.netty.channel.socket.nio.NioServerSocketChannel;import io.netty.handler.logging.LogLevel;import io.netty.handler.logging.LoggingHandler;import java.net.InetSocketAddress;//websocket长连接示例public class Server { public static void main(String[] args) throws Exception{ // 主线程组 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 从线程组 EventLoopGroup wokerGroup = new NioEventLoopGroup(); try{ ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup,wokerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ServerChannelInitializer()); ChannelFuture channelFuture = serverBootstrap.bind(new InetSocketAddress(8899)).sync(); channelFuture.channel().closeFuture().sync(); }finally { bossGroup.shutdownGracefully(); wokerGroup.shutdownGracefully(); } }}ServerChannelInitializer.javaimport com.example.nettydemo.protobuf.MessageData;import com.google.protobuf.MessageLite;import com.google.protobuf.MessageLiteOrBuilder;import io.netty.buffer.ByteBuf;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.ChannelInitializer;import io.netty.channel.ChannelPipeline;import io.netty.channel.socket.SocketChannel;import io.netty.handler.codec.MessageToMessageDecoder;import io.netty.handler.codec.MessageToMessageEncoder;import io.netty.handler.codec.http.HttpObjectAggregator;import io.netty.handler.codec.http.HttpServerCodec;import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;import io.netty.handler.codec.http.websocketx.WebSocketFrame;import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;import io.netty.handler.codec.http.websocketx.extensions.compression.WebSocketServerCompressionHandler;import io.netty.handler.codec.protobuf.ProtobufDecoder;import io.netty.handler.stream.ChunkedWriteHandler;import java.util.List;import static io.netty.buffer.Unpooled.wrappedBuffer;public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); // HTTP请求的解码和编码 pipeline.addLast(new HttpServerCodec()); // 把多个消息转换为一个单一的FullHttpRequest或是FullHttpResponse, // 原因是HTTP解码器会在每个HTTP消息中生成多个消息对象HttpRequest/HttpResponse,HttpContent,LastHttpContent pipeline.addLast(new HttpObjectAggregator(65536)); // 主要用于处理大数据流,比如一个1G大小的文件如果你直接传输肯定会撑暴jvm内存的; 增加之后就不用考虑这个问题了 pipeline.addLast(new ChunkedWriteHandler()); // WebSocket数据压缩 pipeline.addLast(new WebSocketServerCompressionHandler()); // 协议包长度限制 pipeline.addLast(new WebSocketServerProtocolHandler("/ws", null, true)); // 协议包解码 pipeline.addLast(new MessageToMessageDecoder<WebSocketFrame>() { @Override protected void decode(ChannelHandlerContext ctx, WebSocketFrame frame, List<Object> objs) throws Exception { ByteBuf buf = ((BinaryWebSocketFrame) frame).content(); objs.add(buf); buf.retain(); } }); // 协议包编码 pipeline.addLast(new MessageToMessageEncoder<MessageLiteOrBuilder>() { @Override protected void encode(ChannelHandlerContext ctx, MessageLiteOrBuilder msg, List<Object> out) throws Exception { ByteBuf result = null; if (msg instanceof MessageLite) { result = wrappedBuffer(((MessageLite) msg).toByteArray()); } if (msg instanceof MessageLite.Builder) { result = wrappedBuffer(((MessageLite.Builder) msg).build().toByteArray()); } // ==== 上面代码片段是拷贝自TCP ProtobufEncoder 源码 ==== // 然后下面再转成websocket二进制流,因为客户端不能直接解析protobuf编码生成的 WebSocketFrame frame = new BinaryWebSocketFrame(result); out.add(frame); } }); // 协议包解码时指定Protobuf字节数实例化为CommonProtocol类型 pipeline.addLast(new ProtobufDecoder(MessageData.RequestUser.getDefaultInstance())); // websocket定义了传递数据的6中frame类型 pipeline.addLast(new ServerFrameHandler()); }}ServerFrameHandler.javaimport com.example.nettydemo.protobuf.MessageData;import io.netty.buffer.ByteBuf;import io.netty.buffer.Unpooled;import io.netty.channel.Channel;import io.netty.channel.ChannelHandlerContext;import io.netty.channel.SimpleChannelInboundHandler;import io.netty.channel.group.ChannelGroup;import io.netty.channel.group.DefaultChannelGroup;import io.netty.handler.codec.http.websocketx.BinaryWebSocketFrame;import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;import io.netty.handler.codec.http.websocketx.WebSocketFrame;import io.netty.util.concurrent.GlobalEventExecutor;import java.util.List;//处理文本协议数据,处理TextWebSocketFrame类型的数据,websocket专门处理文本的frame就是TextWebSocketFramepublic class ServerFrameHandler extends SimpleChannelInboundHandler<MessageData.RequestUser> { private final ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); //读到客户端的内容并且向客户端去写内容 @Override protected void channelRead0(ChannelHandlerContext ctx, MessageData.RequestUser msg) throws Exception { // channelGroup.add(); Channel channel = ctx.channel(); System.out.println(msg.getUserName()); System.out.println(msg.getAge()); System.out.println(msg.getPassword()); MessageData.ResponseUser bank = MessageData .ResponseUser.newBuilder() .setUserName(“你好,请问有什么可以帮助你!”) .setAge(18).setPassword(“11111”).build(); channel.writeAndFlush(bank); } //每个channel都有一个唯一的id值 @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { //打印出channel唯一值,asLongText方法是channel的id的全名 // System.out.println(“handlerAdded:"+ctx.channel().id().asLongText()); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { // System.out.println(“handlerRemoved:” + ctx.channel().id().asLongText()); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println(“异常发生”); ctx.close(); } }protobuf 文件的使用proto 文件syntax =“proto2”;package com.example.nettydemo.protobuf;//optimize_for 加快解析的速度option optimize_for = SPEED;option java_package = “com.example.nettydemo.protobuf”;option java_outer_classname=“MessageData”;// 客户端发送过来的消息实体message RequestUser{ optional string user_name = 1; optional int32 age = 2; optional string password = 3;}// 返回给客户端的消息实体message ResponseUser{ optional string user_name = 1; optional int32 age = 2; optional string password = 3;}生成 proto 的Java 类批量生成工具,直接找到这个 bat 或者 sh 文件,在对应的平台执行就可以了具体可以自行百度 protobuf 怎么使用Windows 版本set outPath=../../javaset fileArray=(MessageDataProto ATestProto)# 将.proto文件生成java类for %%i in %fileArray% do ( echo generate cli protocol java code: %%i.proto protoc –java_out=%outPath% ./%%i.proto)pausesh 版本 地址: https://github.com/lmxdawn/ne...#!/bin/bashoutPath=../../javafileArray=(MessageDataProto ATestProto)for i in ${fileArray[@]};do echo “generate cli protocol java code: ${i}.proto” protoc –java_out=$outPath ./$i.protodonewebsocket 实现<!DOCTYPE html><html lang=“en”><head> <meta charset=“UTF-8”> <title>WebSocket客户端</title></head><body><script src=“protobuf.min.js”></script><script type=“text/javascript”> var socket; //如果浏览器支持WebSocket if (window.WebSocket) { //参数就是与服务器连接的地址 socket = new WebSocket(“ws://localhost:8899/ws”); //客户端收到服务器消息的时候就会执行这个回调方法 socket.onmessage = function (event) { var ta = document.getElementById(“responseText”); // 解码 responseUserDecoder({ data: event.data, success: function (responseUser) { var content = “客服小姐姐: " + responseUser.userName + “, 小姐姐年龄: " + responseUser.age + “, 密码: " + responseUser.password; ta.value = ta.value + “\n” + content; }, fail: function (err) { console.log(err); }, complete: function () { console.log(“解码全部完成”) } }) } //连接建立的回调函数 socket.onopen = function (event) { var ta = document.getElementById(“responseText”); ta.value = “连接开启”; } //连接断掉的回调函数 socket.onclose = function (event) { var ta = document.getElementById(“responseText”); ta.value = ta.value + “\n” + “连接关闭”; } } else { alert(“浏览器不支持WebSocket!”); } //发送数据 function send(message) { if (!window.WebSocket) { return; } // socket.binaryType = “arraybuffer”; // 判断是否开启 if (socket.readyState !== WebSocket.OPEN) { alert(“连接没有开启”); return; } var data = { userName: message, age: 18, password: “11111” }; requestUserEncoder({ data: data, success: function (buffer) { console.log(“编码成功”); socket.send(buffer); }, fail: function (err) { console.log(err); }, complete: function () { console.log(“编码全部完成”) } }); } /** * 发送的消息编码成 protobuf / function requestUserEncoder(obj) { var data = obj.data; var success = obj.success; // 成功的回调 var fail = obj.fail; // 失败的回调 var complete = obj.complete; // 成功或者失败都会回调 protobuf.load(”../proto/MessageDataProto.proto”, function (err, root) { if (err) { if (typeof fail === “function”) { fail(err) } if (typeof complete === “function”) { complete() } return; } // Obtain a message type var RequestUser = root.lookupType(“com.example.nettydemo.protobuf.RequestUser”); // Exemplary payload var payload = data; // Verify the payload if necessary (i.e. when possibly incomplete or invalid) var errMsg = RequestUser.verify(payload); if (errMsg) { if (typeof fail === “function”) { fail(errMsg) } if (typeof complete === “function”) { complete() } return; } // Create a new message var message = RequestUser.create(payload); // or use .fromObject if conversion is necessary // Encode a message to an Uint8Array (browser) or Buffer (node) var buffer = RequestUser.encode(message).finish(); if (typeof success === “function”) { success(buffer) } if (typeof complete === “function”) { complete() } }); } /* * 接收到服务器二进制流的消息进行解码 */ function responseUserDecoder(obj) { var data = obj.data; var success = obj.success; // 成功的回调 var fail = obj.fail; // 失败的回调 var complete = obj.complete; // 成功或者失败都会回调 protobuf.load(”../proto/MessageDataProto.proto”, function (err, root) { if (err) { if (typeof fail === “function”) { fail(err) } if (typeof complete === “function”) { complete() } return; } // Obtain a message type var ResponseUser = root.lookupType(“com.example.nettydemo.protobuf.ResponseUser”); var reader = new FileReader(); reader.readAsArrayBuffer(data); reader.onload = function (e) { var buf = new Uint8Array(reader.result); var responseUser = ResponseUser.decode(buf); if (typeof success === “function”) { success(responseUser) } if (typeof complete === “function”) { complete() } } }); }</script><h1>欢迎访问客服系统</h1><form onsubmit=“return false”> <textarea name=“message” style=“width: 400px;height: 200px”></textarea> <input type=“button” value=“发送数据” onclick=“send(this.form.message.value);"> <h3>回复消息:</h3> <textarea id=“responseText” style=“width: 400px;height: 300px;"></textarea> <input type=“button” onclick=“javascript:document.getElementById(‘responseText’).value=’’” value=“清空数据”></form></body></html>扩展阅读spring boot 实现的后台管理系统vue + element-ui 实现的后台管理界面,接入 spring boot API接口 ...

December 20, 2018 · 5 min · jiezi

带入gRPC:gRPC Streaming, Client and Server

带入gRPC:gRPC Streaming, Client and Server原文地址:带入gRPC:gRPC Streaming, Client and Server前言本章节将介绍 gRPC 的流式,分为三种类型:Server-side streaming RPC:服务器端流式 RPCClient-side streaming RPC:客户端流式 RPCBidirectional streaming RPC:双向流式 RPC流任何技术,因为有痛点,所以才有了存在的必要性。如果您想要了解 gRPC 的流式调用,请继续图gRPC Streaming 是基于 HTTP/2 的,后续章节再进行详细讲解为什么不用 Simple RPC流式为什么要存在呢,是 Simple RPC 有什么问题吗?通过模拟业务场景,可得知在使用 Simple RPC 时,有如下问题:数据包过大造成的瞬时压力接收数据包时,需要所有数据包都接受成功且正确后,才能够回调响应,进行业务处理(无法客户端边发送,服务端边处理)为什么用 Streaming RPC大规模数据包实时场景模拟场景每天早上 6 点,都有一批百万级别的数据集要同从 A 同步到 B,在同步的时候,会做一系列操作(归档、数据分析、画像、日志等)。这一次性涉及的数据量确实大在同步完成后,也有人马上会去查阅数据,为了新的一天筹备。也符合实时性。两者相较下,这个场景下更适合使用 Streaming RPCgRPC在讲解具体的 gRPC 流式代码时,会着重在第一节讲解,因为三种模式其实是不同的组合。希望你能够注重理解,举一反三,其实都是一样的知识点 ????目录结构$ tree go-grpc-example go-grpc-example├── client│ ├── simple_client│ │ └── client.go│ └── stream_client│ └── client.go├── proto│ ├── search.proto│ └── stream.proto└── server ├── simple_server │ └── server.go └── stream_server └── server.go增加 stream_server、stream_client 存放服务端和客户端文件,proto/stream.proto 用于编写 IDLIDL在 proto 文件夹下的 stream.proto 文件中,写入如下内容:syntax = “proto3”;package proto;service StreamService { rpc List(StreamRequest) returns (stream StreamResponse) {}; rpc Record(stream StreamRequest) returns (StreamResponse) {}; rpc Route(stream StreamRequest) returns (stream StreamResponse) {};}message StreamPoint { string name = 1; int32 value = 2;}message StreamRequest { StreamPoint pt = 1;}message StreamResponse { StreamPoint pt = 1;}注意关键字 stream,声明其为一个流方法。这里共涉及三个方法,对应关系为List:服务器端流式 RPCRecord:客户端流式 RPCRoute:双向流式 RPC基础模板 + 空定义Serverpackage mainimport ( “log” “net” “google.golang.org/grpc” pb “github.com/EDDYCJY/go-grpc-example/proto” )type StreamService struct{}const ( PORT = “9002”)func main() { server := grpc.NewServer() pb.RegisterStreamServiceServer(server, &StreamService{}) lis, err := net.Listen(“tcp”, “:"+PORT) if err != nil { log.Fatalf(“net.Listen err: %v”, err) } server.Serve(lis)}func (s *StreamService) List(r *pb.StreamRequest, stream pb.StreamService_ListServer) error { return nil}func (s *StreamService) Record(stream pb.StreamService_RecordServer) error { return nil}func (s *StreamService) Route(stream pb.StreamService_RouteServer) error { return nil}写代码前,建议先将 gRPC Server 的基础模板和接口给空定义出来。若有不清楚可参见上一章节的知识点Clientpackage mainimport ( “log” “google.golang.org/grpc” pb “github.com/EDDYCJY/go-grpc-example/proto”)const ( PORT = “9002”)func main() { conn, err := grpc.Dial(”:"+PORT, grpc.WithInsecure()) if err != nil { log.Fatalf(“grpc.Dial err: %v”, err) } defer conn.Close() client := pb.NewStreamServiceClient(conn) err = printLists(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: “gRPC Stream Client: List”, Value: 2018}}) if err != nil { log.Fatalf(“printLists.err: %v”, err) } err = printRecord(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: “gRPC Stream Client: Record”, Value: 2018}}) if err != nil { log.Fatalf(“printRecord.err: %v”, err) } err = printRoute(client, &pb.StreamRequest{Pt: &pb.StreamPoint{Name: “gRPC Stream Client: Route”, Value: 2018}}) if err != nil { log.Fatalf(“printRoute.err: %v”, err) }}func printLists(client pb.StreamServiceClient, r *pb.StreamRequest) error { return nil}func printRecord(client pb.StreamServiceClient, r *pb.StreamRequest) error { return nil}func printRoute(client pb.StreamServiceClient, r *pb.StreamRequest) error { return nil}一、Server-side streaming RPC:服务器端流式 RPC服务器端流式 RPC,显然是单向流,并代指 Server 为 Stream 而 Client 为普通 RPC 请求简单来讲就是客户端发起一次普通的 RPC 请求,服务端通过流式响应多次发送数据集,客户端 Recv 接收数据集。大致如图:Serverfunc (s *StreamService) List(r *pb.StreamRequest, stream pb.StreamService_ListServer) error { for n := 0; n <= 6; n++ { err := stream.Send(&pb.StreamResponse{ Pt: &pb.StreamPoint{ Name: r.Pt.Name, Value: r.Pt.Value + int32(n), }, }) if err != nil { return err } } return nil}在 Server,主要留意 stream.Send 方法。它看上去能发送 N 次?有没有大小限制?type StreamService_ListServer interface { Send(*StreamResponse) error grpc.ServerStream}func (x *streamServiceListServer) Send(m *StreamResponse) error { return x.ServerStream.SendMsg(m)}通过阅读源码,可得知是 protoc 在生成时,根据定义生成了各式各样符合标准的接口方法。最终再统一调度内部的 SendMsg 方法,该方法涉及以下过程:消息体(对象)序列化压缩序列化后的消息体对正在传输的消息体增加 5 个字节的 header判断压缩+序列化后的消息体总字节长度是否大于预设的 maxSendMessageSize(预设值为 math.MaxInt32),若超出则提示错误写入给流的数据集Clientfunc printLists(client pb.StreamServiceClient, r *pb.StreamRequest) error { stream, err := client.List(context.Background(), r) if err != nil { return err } for { resp, err := stream.Recv() if err == io.EOF { break } if err != nil { return err } log.Printf(“resp: pj.name: %s, pt.value: %d”, resp.Pt.Name, resp.Pt.Value) } return nil}在 Client,主要留意 stream.Recv() 方法。什么情况下 io.EOF ?什么情况下存在错误信息呢?type StreamService_ListClient interface { Recv() (*StreamResponse, error) grpc.ClientStream}func (x *streamServiceListClient) Recv() (*StreamResponse, error) { m := new(StreamResponse) if err := x.ClientStream.RecvMsg(m); err != nil { return nil, err } return m, nil}通过阅读源码,可得知:当流结束(调用了 Close)时,会出现 io.EOF。而错误信息(err)基本都由另一侧反馈过来,因此进行日常处理和标记即可验证运行 stream_server/server.go:$ go run server.go运行 stream_client/client.go:$ go run client.go 2018/09/24 16:18:25 resp: pj.name: gRPC Stream Client: List, pt.value: 20182018/09/24 16:18:25 resp: pj.name: gRPC Stream Client: List, pt.value: 20192018/09/24 16:18:25 resp: pj.name: gRPC Stream Client: List, pt.value: 20202018/09/24 16:18:25 resp: pj.name: gRPC Stream Client: List, pt.value: 20212018/09/24 16:18:25 resp: pj.name: gRPC Stream Client: List, pt.value: 20222018/09/24 16:18:25 resp: pj.name: gRPC Stream Client: List, pt.value: 20232018/09/24 16:18:25 resp: pj.name: gRPC Stream Client: List, pt.value: 2024二、Client-side streaming RPC:客户端流式 RPC客户端流式 RPC,单向流,客户端通过流式发起多次 RPC 请求给服务端,服务端发起一次响应给客户端,大致如图:Serverfunc (s *StreamService) Record(stream pb.StreamService_RecordServer) error { for { r, err := stream.Recv() if err == io.EOF { return stream.SendAndClose(&pb.StreamResponse{Pt: &pb.StreamPoint{Name: “gRPC Stream Server: Record”, Value: 1}}) } if err != nil { return err } log.Printf(“stream.Recv pt.name: %s, pt.value: %d”, r.Pt.Name, r.Pt.Value) } return nil}多了一个从未见过的方法 stream.SendAndClose,它是做什么用的呢?在这段程序中,我们对每一个 Recv 都进行了处理,当发现 io.EOF (流关闭) 后,需要将最终的响应结果发送给客户端,同时关闭正在另外一侧等待的 RecvClientfunc printRecord(client pb.StreamServiceClient, r *pb.StreamRequest) error { stream, err := client.Record(context.Background()) if err != nil { return err } for n := 0; n < 6; n++ { err := stream.Send(r) if err != nil { return err } } resp, err := stream.CloseAndRecv() if err != nil { return err } log.Printf(“resp: pj.name: %s, pt.value: %d”, resp.Pt.Name, resp.Pt.Value) return nil}stream.CloseAndRecv 和 stream.SendAndClose 是配套使用的流方法,相信聪明的你已经秒懂它的作用了验证重启 stream_server/server.go,再次运行 stream_client/client.go:stream_client:$ go run client.go2018/09/24 16:23:03 resp: pj.name: gRPC Stream Server: Record, pt.value: 1stream_server:$ go run server.go2018/09/24 16:23:03 stream.Recv pt.name: gRPC Stream Client: Record, pt.value: 20182018/09/24 16:23:03 stream.Recv pt.name: gRPC Stream Client: Record, pt.value: 20182018/09/24 16:23:03 stream.Recv pt.name: gRPC Stream Client: Record, pt.value: 20182018/09/24 16:23:03 stream.Recv pt.name: gRPC Stream Client: Record, pt.value: 20182018/09/24 16:23:03 stream.Recv pt.name: gRPC Stream Client: Record, pt.value: 20182018/09/24 16:23:03 stream.Recv pt.name: gRPC Stream Client: Record, pt.value: 2018三、Bidirectional streaming RPC:双向流式 RPC双向流式 RPC,顾名思义是双向流。由客户端以流式的方式发起请求,服务端同样以流式的方式响应请求首个请求一定是 Client 发起,但具体交互方式(谁先谁后、一次发多少、响应多少、什么时候关闭)根据程序编写的方式来确定(可以结合协程)因此图示也千变万化,这里就不放出来了Serverfunc (s *StreamService) Route(stream pb.StreamService_RouteServer) error { n := 0 for { err := stream.Send(&pb.StreamResponse{ Pt: &pb.StreamPoint{ Name: “gPRC Stream Client: Route”, Value: int32(n), }, }) if err != nil { return err } r, err := stream.Recv() if err == io.EOF { return nil } if err != nil { return err } n++ log.Printf(“stream.Recv pt.name: %s, pt.value: %d”, r.Pt.Name, r.Pt.Value) } return nil}Clientfunc printRoute(client pb.StreamServiceClient, r *pb.StreamRequest) error { stream, err := client.Route(context.Background()) if err != nil { return err } for n := 0; n <= 6; n++ { err = stream.Send(r) if err != nil { return err } resp, err := stream.Recv() if err == io.EOF { break } if err != nil { return err } log.Printf(“resp: pj.name: %s, pt.value: %d”, resp.Pt.Name, resp.Pt.Value) } stream.CloseSend() return nil}验证重启 stream_server/server.go,再次运行 stream_client/client.go:stream_server$ go run server.go2018/09/24 16:29:43 stream.Recv pt.name: gRPC Stream Client: Route, pt.value: 20182018/09/24 16:29:43 stream.Recv pt.name: gRPC Stream Client: Route, pt.value: 20182018/09/24 16:29:43 stream.Recv pt.name: gRPC Stream Client: Route, pt.value: 20182018/09/24 16:29:43 stream.Recv pt.name: gRPC Stream Client: Route, pt.value: 20182018/09/24 16:29:43 stream.Recv pt.name: gRPC Stream Client: Route, pt.value: 20182018/09/24 16:29:43 stream.Recv pt.name: gRPC Stream Client: Route, pt.value: 2018stream_client$ go run client.go2018/09/24 16:29:43 resp: pj.name: gPRC Stream Client: Route, pt.value: 02018/09/24 16:29:43 resp: pj.name: gPRC Stream Client: Route, pt.value: 12018/09/24 16:29:43 resp: pj.name: gPRC Stream Client: Route, pt.value: 22018/09/24 16:29:43 resp: pj.name: gPRC Stream Client: Route, pt.value: 32018/09/24 16:29:43 resp: pj.name: gPRC Stream Client: Route, pt.value: 42018/09/24 16:29:43 resp: pj.name: gPRC Stream Client: Route, pt.value: 52018/09/24 16:29:43 resp: pj.name: gPRC Stream Client: Route, pt.value: 6总结在本文共介绍了三类流的交互方式,可以根据实际的业务场景去选择合适的方式。会事半功倍哦 ????系列目录带入gRPC:gRPC及相关介绍带入gRPC:gRPC Client and Server带入gRPC:gRPC Streaming, Client and Server ...

September 25, 2018 · 5 min · jiezi

带入gRPC:gRPC Client and Server

带入gRPC:gRPC Client and Server原文地址:带入gRPC:gRPC Client and Server前言本章节将使用 Go 来编写 gRPC Server 和 Client,让其互相通讯。在此之上会使用到如下库:google.golang.org/grpcgithub.com/golang/protobuf/protoc-gen-go安装gRPCgo get -u google.golang.org/grpcProtocol Buffers v3wget https://github.com/google/protobuf/releases/download/v3.5.1/protobuf-all-3.5.1.zipunzip protobuf-all-3.5.1.zipcd protobuf-3.5.1/./configuremakemake install检查是否安装成功protoc –version若出现以下错误,执行 ldconfig 命名就能解决这问题protoc: error while loading shared libraries: libprotobuf.so.15: cannot open shared object file: No such file or directoryProtoc Plugingo get -u github.com/golang/protobuf/protoc-gen-go安装环境若有问题,可参考我先前的文章 《介绍与环境安装》 内有详细介绍,不再赘述gRPC本小节开始正式编写 gRPC 相关的程序,一起上车吧 ????目录结构$ tree go-grpc-example go-grpc-example├── client├── proto│ └── search.proto└── server.goIDL编写在 proto 文件夹下的 search.proto 文件中,写入如下内容:syntax = “proto3”;package proto;service SearchService { rpc Search(SearchRequest) returns (SearchResponse) {}}message SearchRequest { string request = 1;}message SearchResponse { string response = 1;}生成在 proto 文件夹下执行如下命令:$ protoc –go_out=plugins=grpc:. *.protoplugins=plugin1+plugin2:指定要加载的子插件列表我们定义的 proto 文件是涉及了 RPC 服务的,而默认是不会生成 RPC 代码的,因此需要给出 plugins 参数传递给 protoc-gen-go,告诉它,请支持 RPC(这里指定了 gRPC)–go_out=.:设置 Go 代码输出的目录该指令会加载 protoc-gen-go 插件达到生成 Go 代码的目的,生成的文件以 .pb.go 为文件后缀: (冒号)冒号充当分隔符的作用,后跟所需要的参数集。如果这处不涉及 RPC,命令可简化为:$ protoc –go_out=. *.proto注:建议你看看两条命令生成的 .pb.go 文件,分别有什么区别生成后执行完毕命令后,将得到一个 .pb.go 文件,文件内容如下:type SearchRequest struct { Request string protobuf:"bytes,1,opt,name=request" json:"request,omitempty" XXX_NoUnkeyedLiteral struct{} json:"-" XXX_unrecognized []byte json:"-" XXX_sizecache int32 json:"-"}func (m *SearchRequest) Reset() { *m = SearchRequest{} }func (m *SearchRequest) String() string { return proto.CompactTextString(m) }func (*SearchRequest) ProtoMessage() {}func (*SearchRequest) Descriptor() ([]byte, []int) { return fileDescriptor_search_8b45f79ee13ff6a3, []int{0}}func (m *SearchRequest) GetRequest() string { if m != nil { return m.Request } return “"}通过阅读这一部分代码,可以知道主要涉及如下方面:字段名称从小写下划线转换为大写驼峰模式(字段导出)生成一组 Getters 方法,能便于处理一些空指针取值的情况ProtoMessage 方法实现 proto.Message 的接口生成 Rest 方法,便于将 Protobuf 结构体恢复为零值Repeated 转换为切片type SearchRequest struct { Request string protobuf:"bytes,1,opt,name=request" json:"request,omitempty"}func (*SearchRequest) Descriptor() ([]byte, []int) { return fileDescriptor_search_8b45f79ee13ff6a3, []int{0}}type SearchResponse struct { Response string protobuf:"bytes,1,opt,name=response" json:"response,omitempty"}func (*SearchResponse) Descriptor() ([]byte, []int) { return fileDescriptor_search_8b45f79ee13ff6a3, []int{1}}…func init() { proto.RegisterFile(“search.proto”, fileDescriptor_search_8b45f79ee13ff6a3) }var fileDescriptor_search_8b45f79ee13ff6a3 = []byte{ // 131 bytes of a gzipped FileDescriptorProto 0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0xe2, 0xe2, 0x29, 0x4e, 0x4d, 0x2c, 0x4a, 0xce, 0xd0, 0x2b, 0x28, 0xca, 0x2f, 0xc9, 0x17, 0x62, 0x05, 0x53, 0x4a, 0x9a, 0x5c, 0xbc, 0xc1, 0x60, 0xe1, 0xa0, 0xd4, 0xc2, 0xd2, 0xd4, 0xe2, 0x12, 0x21, 0x09, 0x2e, 0xf6, 0x22, 0x08, 0x53, 0x82, 0x51, 0x81, 0x51, 0x83, 0x33, 0x08, 0xc6, 0x55, 0xd2, 0xe1, 0xe2, 0x83, 0x29, 0x2d, 0x2e, 0xc8, 0xcf, 0x2b, 0x4e, 0x15, 0x92, 0xe2, 0xe2, 0x28, 0x82, 0xb2, 0xa1, 0x8a, 0xe1, 0x7c, 0x23, 0x0f, 0x98, 0xc1, 0xc1, 0xa9, 0x45, 0x65, 0x99, 0xc9, 0xa9, 0x42, 0xe6, 0x5c, 0x6c, 0x10, 0x01, 0x21, 0x11, 0x88, 0x13, 0xf4, 0x50, 0x2c, 0x96, 0x12, 0x45, 0x13, 0x85, 0x98, 0xa3, 0xc4, 0x90, 0xc4, 0x06, 0x16, 0x37, 0x06, 0x04, 0x00, 0x00, 0xff, 0xff, 0xf3, 0xba, 0x74, 0x95, 0xc0, 0x00, 0x00, 0x00,}而这一部分代码主要是围绕 fileDescriptor 进行,在这里 fileDescriptor_search_8b45f79ee13ff6a3 表示一个编译后的 proto 文件,而每一个方法都包含 Descriptor 方法,代表着这一个方法在 fileDescriptor 中具体的 Message FieldServer这一小节将编写 gRPC Server 的基础模板,完成一个方法的调用。对 server.go 写入如下内容:package mainimport ( “context” “log” “net” “google.golang.org/grpc” pb “github.com/EDDYCJY/go-grpc-example/proto”)type SearchService struct{}func (s *SearchService) Search(ctx context.Context, r *pb.SearchRequest) (*pb.SearchResponse, error) { return &pb.SearchResponse{Response: r.GetRequest() + " Server”}, nil}const PORT = “9001"func main() { server := grpc.NewServer() pb.RegisterSearchServiceServer(server, &SearchService{}) lis, err := net.Listen(“tcp”, “:"+PORT) if err != nil { log.Fatalf(“net.Listen err: %v”, err) } server.Serve(lis)}创建 gRPC Server 对象,你可以理解为它是 Server 端的抽象对象将 SearchService(其包含需要被调用的服务端接口)注册到 gRPC Server 的内部注册中心。这样可以在接受到请求时,通过内部的服务发现,发现该服务端接口并转接进行逻辑处理创建 Listen,监听 TCP 端口gRPC Server 开始 lis.Accept,直到 Stop 或 GracefulStopClient接下来编写 gRPC Go Client 的基础模板,打开 client/client.go 文件,写入以下内容:package mainimport ( “context” “log” “google.golang.org/grpc” pb “github.com/EDDYCJY/go-grpc-example/proto”)const PORT = “9001"func main() { conn, err := grpc.Dial(”:"+PORT, grpc.WithInsecure()) if err != nil { log.Fatalf(“grpc.Dial err: %v”, err) } defer conn.Close() client := pb.NewSearchServiceClient(conn) resp, err := client.Search(context.Background(), &pb.SearchRequest{ Request: “gRPC”, }) if err != nil { log.Fatalf(“client.Search err: %v”, err) } log.Printf(“resp: %s”, resp.GetResponse())}创建与给定目标(服务端)的连接交互创建 SearchService 的客户端对象发送 RPC 请求,等待同步响应,得到回调后返回响应结果输出响应结果验证启动 Server$ pwd$GOPATH/github.com/EDDYCJY/go-grpc-example$ go run server.go启动 Client$ pwd $GOPATH/github.com/EDDYCJY/go-grpc-example/client$ go run client.go 2018/09/23 11:06:23 resp: gRPC Server系列目录带入gRPC:gRPC及相关介绍带入gRPC:gRPC Client and Server总结在本章节,我们对 Protobuf、gRPC Client/Server 分别都进行了介绍。希望你结合文中讲述内容再写一个 Demo 进行深入了解,肯定会更棒 ???? ...

September 24, 2018 · 3 min · jiezi

带入gRPC:gRPC及相关介绍

带入gRPC:gRPC及相关介绍原文地址:带入gRPC:gRPC及相关介绍作为开篇章,将会介绍 gRPC 相关的一些知识。简单来讲 gRPC 是一个 基于 HTTP/2 协议设计的 RPC 框架,它采用了 Protobuf 作为 IDL你是否有过疑惑,它们都是些什么?本文将会介绍一些常用的知识和概念,更详细的会给出手册地址去深入一、RPC什么是 RPCRPC 代指远程过程调用(Remote Procedure Call),它的调用包含了传输协议和编码(对象序列号)协议等等。允许运行于一台计算机的程序调用另一台计算机的子程序,而开发人员无需额外地为这个交互作用编程实际场景:有两台服务器,分别是A、B。在 A 上的应用 C 想要调用 B 服务器上的应用 D,它们可以直接本地调用吗? 答案是不能的,但走 RPC 的话,十分方便。因此常有人称使用 RPC,就跟本地调用一个函数一样简单RPC 框架我认为,一个完整的 RPC 框架,应包含负载均衡、服务注册和发现、服务治理等功能,并具有可拓展性便于流量监控系统等接入 那么它才算完整的,当然了。有些较单一的 RPC 框架,通过组合多组件也能达到这个标准你认为呢?常见 RPC 框架gRPCThriftRpcxDubbo比较一下\跨语言多 IDL服务治理注册中心服务管理gRPC√××××Thrift√××××Rpcx×√√√√Dubbo×√√√√为什么要 RPC简单、通用、安全、效率RPC 可以基于 HTTP 吗RPC 是代指远程过程调用,是可以基于 HTTP 协议的肯定会有人说效率优势,我可以告诉你,那是基于 HTTP/1.1 来讲的,HTTP/2 优化了许多问题(当然也存在新的问题),所以你看到了本文的主题 gRPC二、Protobuf介绍Protocol Buffers 是一种与语言、平台无关,可扩展的序列化结构化数据的方法,常用于通信协议,数据存储等等。相较于 JSON、XML,它更小、更快、更简单,因此也更受开发人员的青眯语法syntax = “proto3”;service SearchService { rpc Search (SearchRequest) returns (SearchResponse);}message SearchRequest { string query = 1; int32 page_number = 2; int32 result_per_page = 3;}message SearchResponse { …}1、第一行(非空的非注释行)声明使用 proto3 语法。如果不声明,将默认使用 proto2 语法。同时我建议用 v2 还是 v3,都应当声明其使用的版本2、定义 SearchService RPC 服务,其包含 RPC 方法 Search,入参为 SearchRequest 消息,出参为 SearchResponse 消息3、定义 SearchRequest、SearchResponse 消息,前者定义了三个字段,每一个字段包含三个属性:类型、字段名称、字段编号4、Protobuf 编译器会根据选择的语言不同,生成相应语言的 Service Interface Code 和 Stubs最后,这里只是简单的语法介绍,详细的请右拐 Language Guide (proto3)数据类型.proto TypeC++ TypeJava TypeGo TypePHP Typedoubledoubledoublefloat64floatfloatfloatfloatfloat32floatint32int32intint32integerint64int64longint64integer/stringuint32uint32intuint32integeruint64uint64longuint64integer/stringsint32int32intint32integersint64int64longint64integer/stringfixed32uint32intuint32integerfixed64uint64longuint64integer/stringsfixed32int32intint32integersfixed64int64longint64integer/stringboolboolbooleanboolbooleanstringstringStringstringstringbytesstringByteString[]bytestringv2 和 v3 主要区别删除原始值字段的字段存在逻辑删除 required 字段删除 optional 字段,默认就是删除 default 字段删除扩展特性,新增 Any 类型来替代它删除 unknown 字段的支持新增 JSON Mapping新增 Map 类型的支持修复 enum 的 unknown 类型repeated 默认使用 packed 编码引入了新的语言实现(C#,JavaScript,Ruby,Objective-C)以上是日常涉及的常见功能,如果还想详细了解可阅读 Protobuf Version 3.0.0相较 Protobuf,为什么不使用XML?更简单数据描述文件只需原来的1/10至1/3解析速度是原来的20倍至100倍减少了二义性生成了更易使用的数据访问类三、gRPC介绍gRPC 是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计多语言C++C#DartGoJavaNode.jsObjective-CPHPPythonRuby特点1、HTTP/22、Protobuf3、客户端、服务端基于同一份 IDL 4、移动网络的良好支持5、支持多语言概览讲解1、客户端(gRPC Sub)调用 A 方法,发起 RPC 调用 2、对请求信息使用 Protobuf 进行对象序列化压缩(IDL)3、服务端(gRPC Server)接收到请求后,解码请求体,进行业务逻辑处理并返回4、对响应结果使用 Protobuf 进行对象序列化压缩(IDL)5、客户端接受到服务端响应,解码请求体。回调被调用的 A 方法,唤醒正在等待响应(阻塞)的客户端调用并返回响应结果示例在这一小节,将简单的给大家展示 gRPC 的客户端和服务端的示例代码,希望大家先有一个基础的印象,将会在下一章节详细介绍 ????构建和启动服务端lis, err := net.Listen(“tcp”, fmt.Sprintf(":%d", *port))if err != nil { log.Fatalf(“failed to listen: %v”, err)}grpcServer := grpc.NewServer()…pb.RegisterSearchServer(grpcServer, &SearchServer{})grpcServer.Serve(lis)1、监听指定 TCP 端口,用于接受客户端请求2、创建 gRPC Server 的实例对象3、gRPC Server 内部服务和路由的注册4、Serve() 调用服务器以执行阻塞等待,直到进程被终止或被 Stop() 调用创建客户端var opts []grpc.DialOption…conn, err := grpc.Dial(*serverAddr, opts…)if err != nil { log.Fatalf(“fail to dial: %v”, err)}defer conn.Close()client := pb.NewSearchClient(conn)…1、创建 gRPC Channel 与 gRPC Server 进行通信(需服务器地址和端口作为参数)2、设置 DialOptions 凭证(例如,TLS,GCE凭据,JWT凭证)3、创建 Search Client Stub4、调用对应的服务方法思考题1、什么场景下不适合使用 Protobuf,而适合使用 JSON、XML?2、Protobuf 一节中提到的 packed 编码,是什么?总结在开篇内容中,我利用了尽量简短的描述给你介绍了接下来所必须、必要的知识点希望你能够有所收获,建议能到我给的参考资料处进行深入学习,是最好的了系列目录带入gRPC:gRPC及相关介绍带入gRPC:gRPC Client and Server参考资料Protocol BuffersgRPC ...

September 24, 2018 · 2 min · jiezi