qianfeng
beego gin iris protobuf
资料链接
github.com/rubyhan1314
package main
import (
"net/rpc"
"net/http"
"net"
"math"
)
type MathUtil struct{
}
func (m * MathUtil) CalculateCircleArea(req float64,resp *float64) error {
*resp = math.Pi * req * req
return nil
}
func main(){
var (
MathUtils *MathUtil //结构体指针
err error
listen net.Listener
)
//1. 初始化指针类型
MathUtils = new(MathUtil)
//2.调用net/rpc 进行服务注册
if err = rpc.Register(MathUtils); err != nil{
panic(err.Error())
return
}
//3. 将函数注册懂HTTP上,可调用
rpc.HandleHTTP()
//4.在特定的端口进行监听
if listen, err = net.Listen("tcp",":8081");err != nil{
return
}
//listen = listen
//4.Serve会接手监听器l收到的每一个连接,
//并为每一个连接创建一个新的服务go程。该go程会读取请求,然后调用srv.Handler回复请求。
if err = http.Serve(listen,nil);err != nil{
return
}
}
package main
import (
"fmt"
"net/rpc"
)
func main(){
var (
client *rpc.Client
err error
req float64
resp *float64
done *rpc.Call
sync1 *rpc.Call
)
if client ,err = rpc.DialHTTP("tcp",":8081");nil != err{
panic(err.Error())
}
req = 3.2
//同步调用方式
// if err = client.Call("MathUtil.CalculateCircleArea",req,&resp); nil != err{
// panic(err.Error())
// }
// fmt.Println(*resp)
//异步调用方式
/*
Go异步的调用函数。本方法Call结构体类型指针的返回值代表该次远程调用。
通道类型的参数done会在本次调用完成时发出信号(通过返回本次Go方法的返回值)。
如果done为nil,Go会申请一个新的通道(写入返回值的Done字段);
如果done非nil,done必须有缓冲,否则Go方法会故意崩溃。
func (client *Client) Go(serviceMethod string, args interface{}, reply interface{},
done chan *Call) *Call
*/
sync1 = client.Go("MathUtil.CalculateCircleArea",req,&resp,nil)
//异步调用 通过Done通道获取结果
done = <-sync1.Done
fmt.Println(done)
if err = client.Close(); nil != err{
panic(err.Error())
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f1vjNN8H-1595164283883)(grpc.assets/1588578657962.png)]
git clone xxxxxgrpc-go
安装
第一种
go get -u https://github.com/grpc/grpc-go
第二种
git clone xxxxx
解压
改名
gprc-go
放到src/google.goland.org/grpc 改下名
资料 qianfeng 官网
[Go语言微服务从入门到大师](javascript:
Google Protocol Buffer( 简称 Protobuf)是Google公司内部的混合语言数据标准,他们主要用于RPC系统和持续数据存储系统。
Protocol Buffers 是一种轻便高效的结构化数据存储格式,可以用于结构化数据串行化,或者说序列化。它很适合做数据存储或RPC数据交换格式。可用于通讯协议、数据存储等领域的语言无关、平台无关、可扩展的序列化结构数据格式。
简单来说,Protobuf的功能类似于XML,即负责把某种数据结构的信息,以某种格式保存起来。主要用于数据存储、传输协议等使用场景。
为什么已经有了XLM,JSON等已经很普遍的数据传输方式,还要设计出Protobuf这样一种新的数据协议呢?
性能好/效率高
整体而言,Protobuf以高效的二进制方式存储,比XML小3到10倍,快20到100倍。
代码生成机制
代码生成机制的含义
在Go语言中,可以通过定义结构体封装描述一个对象,并构造一个新的结构体对象。比如定义Person结构体,并存放于Person.go文件:
type Person struct{
Name string
Age int
Sex int
}
在分布式系统中,因为程序代码时分开部署,比如分别为A、B。A系统在调用B系统时,无法直接采用代码的方式进行调用,因为A系统中不存在B系统中的代码。因此,A系统只负责将调用和通信的数据以二进制数据包的形式传递给B系统,由B系统根据获取到的数据包,自己构建出对应的数据对象,生成数据对象定义代码文件。这种利用编译器,根据数据文件自动生成结构体定义和相关方法的文件的机制被称作代码生成机制。
代码生成机制的优点 首先,代码生成机制能够极大解放开发者编写数据协议解析过程的时间,提高工作效率;其次,易于开发者维护和迭代,当需求发生变更时,开发者只需要修改对应的数据传输文件内容即可完成所有的修改。
支持“向后兼容”和“向前兼容”
支持前后兼容是非常重要的一个特点,在庞大的系统开发中,往往不可能统一完成所有模块的升级,为了保证系统功能正常不受影响,应最大限度保证通讯协议的向前兼容和向后兼容。
支持多种编程语言 Protobuf不仅仅Google开源的一个数据协议,还有很多种语言的开源项目实现。在Google官方发布的Protobuf的源代码中包含了C++、Java、Python三种语言。本系列课程中,我们学习如何实现Golang语言中的功能实现。
Go语言中有对应的实现Protobuf协议的库,Github地址:https://github.com/golang/protobuf
使用Go语言的Protobuf库之前,需要相应的环境准备:
地址
https://github.com/golang/protobuf
安装地址
https://github.com/golang/protobuf/releases
下载对应版本
解压 放到环境变量中
mac 直接将bin下可执行文件,放到 usr/local/include 中就可以
protoc 测试
通过如下命令安装protoc-go库:
go get https://github.com/protocolbuffers/protobuf-go
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-GeCLCKnb-1595164283888)(https://github.com/rubyhan1314/Golang-100-Days/raw/master/Day77(protobuf)]/img/protobuf.png)
安装完成以后,protoc-go*可执行文件在本地环境GOPATH/bin目录下,如下图所示: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0i8dLio7-1595164283889)(https://github.com/rubyhan1314/Golang-100-Days/raw/master/Day77(protobuf)]/img/[email protected])
Protobuf 协议的格式 Protobuf协议规定:使用该协议进行数据序列化和反序列化操作时,首先定义传输数据的格式,并命名为以**".proto"**为扩展名的消息定义文件。
message 定义一个消息 先来看一个非常简单的例子。假设想定义一个“订单”的消息格式,每一个“订单"都含有一个订单号ID、订单金额Num、订单时间TimeStamp字段。可以采用如下的方式来定义消息类型的.proto文件:
message Order{
required string order_id = 1;
required int64 num = 2;
optional int32 timestamp = 3;
}
Order消息格式有3个字段,在消息中承载的数据分别对应每一个字段。其中每个字段都有一个名字和一种类型。 * **指定字段类型:**在proto协议中,字段的类型包括字符串(string)、整形(int32、int64…)、枚举(enum)等数据类型 * **分配标识符:**在消息字段中,每个字段都有唯一的一个标识符。最小的标识号可以从1开始,最大到536870911。不可以使用其中的[19000-19999]的标识号, Protobuf协议实现中对这些进行了预留。如果非要在.proto文件中使用这些预留标识号,编译时就会报警。 * **指定字段规则:**字段的修饰符包含三种类型,分别是: * **required:**一个格式良好的消息一定要含有1个这种字段。表示该值是必须要设置的; * **optional:**消息格式中该字段可以有0个或1个值(不超过1个)。 * **repeated:**在一个格式良好的消息中,这种字段可以重复任意多次(包括0次)。重复的值的顺序会被保留。表示该值可以重复,相当于Go中的slice。
**【注意:】使用required弊多于利;在实际开发中更应该使用optional和repeated而不是required。**
* 添加更多消息类型
在同一个.proto文件中,可以定义多个消息类型。多个消息类型分开定义即可。
1、创建扩展名为**.proto**的文件,并编写代码。比如创建person.proto文件,内容如下:
syntax = "proto2";
package example;
message Person {
required string Name = 1;
required int32 Age = 2;
required string From = 3;
}
2、编译.proto文件,生成Go语言文件。执行如下命令:
protoc --go_out = . test.proto
执行 protoc --go_out=. test.proto 生成对应的 person.pb.go 文件。并构建对应的example目录,存放生成的person.pb.go文件。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IoAnR6QA-1595164283890)(https://camo.githubusercontent.com/2816ab264aad337c737e1d062966a364b99563a1/687474703a2f2f3778746377642e636f6d312e7a302e676c622e636c6f7564646e2e636f6d2f575832303139303630352d3130313433384032782e706e67)]
3、在程序中使用Protobuf 在程序中有如下代码:
package main
import (
"fmt"
"ProtocDemo/example"
"github.com/golang/protobuf/proto"
"os"
)
func main() { fmt.Println(“Hello World. \n”)
msg_test := &example.Person{
Name: proto.String("Davie"),
Age: proto.Int(18),
From: proto.String("China"),
}
//序列化
msgDataEncoding, err := proto.Marshal(msg_test)
if err != nil {
panic(err.Error())
return
}
msgEntity := example.Person{}
err = proto.Unmarshal(msgDataEncoding, &msgEntity)
if err != nil {
fmt.Println(err.Error())
os.Exit(1)
return
}
fmt.Printf("姓名:%s\n\n", msgEntity.GetName())
fmt.Printf("年龄:%d\n\n", msgEntity.GetAge())
fmt.Printf("国籍:%s\n\n", msgEntity.GetFrom())
} ```
message: Protobuf中定义一个数据结构需要用到关键字message,这一点和Java的class,Go语言中的struct类似。
标识号: 在消息的定义中,每个字段等号后面都有唯一的标识号,用于在反序列化过程中识别各个字段的,一旦开始使用就不能改变。标识号从整数1开始,依次递增,每次增加1,标识号的范围为1~2^29 - 1,其中[19000-19999]为Protobuf协议预留字段,开发者不建议使用该范围的标识号;一旦使用,在编译时Protoc编译器会报出警告。
字段规则: 字段规则有三种:
数据类型: 常见的数据类型与protoc协议中的数据类型映射如下:
| .proto类型 | Java类型 | C++类型 | Go语言类型 | 备注 | |-----------|-----------|---------|---------|------------------------------------------| | double | double | double | float64 | | | float | float | float | float32 | | | int32 | int | int | int32 | 可变长编码方式。编码负数时不够高效,如果字段可能包含负数,可以使用sint32 | | int64 | long | int64 | int64 | 可变长编码方式。编码负数时不够高效,如果字段可能包含负数,使用sint64。 | | uint32 | int[1] | uint32 | uint32 | | | uint64 | | uint64 | uint64 | | | sint32 | int | int32 | int32 | 可变长编码方式,有符号的整形值。编码时比int32效率高。 | | sint64 | long | int64 | int64 | 可变长编码方式,有符号的整形值,编码时比int64效率高。 | | fixed32 | int[1] | uint32 | uint32 | 总是4个字节。如果所有数值均比(2^28)大,该种编码方式比uint32高效。 | | fixed64 | long[1] | uint64 | uint64 | 总是8个字节。如果所有数值均比(2^56)大,此种编码方式比uint64高效。 | | sfixed32 | int | uint32 | int32 | 总是4个字节。 | | sfixed64 | long | uint64 | int64 | 总是8个字节。 | | bool | boolean | bool | bool | | | string | String | String | string | |
枚举类型: proto协议支持使用枚举类型,和正常的编程语言一样,枚举类型可以使用enum关键字定义在.proto文件中:
enum Age{
male=1;
female=2;
}
字段默认值: .proto文件支持在进行message定义时设置字段的默认值,可以通过default进行设置,如下所示:
message Address {
required sint32 id = 1 [default = 1];
required string name = 2 [default = '北京'];
optional string pinyin = 3 [default = 'beijing'];
required string address = 4;
required bool flag = 5 [default = true];
}
导入: 如果需要引用的message是写在别的.proto文件中,可以通过import "xxx.proto"来进行引入:
嵌套: message与message之间可以嵌套定义,比如如下形式:
syntax = "proto2";
package example;
message Person {
required string Name = 1;
required int32 Age = 2;
required string From = 3;
optional Address Addr = 4;
message Address {
required sint32 id = 1;
required string name = 2;
optional string pinyin = 3;
required string address = 4;
}
}
message更新规则: message定义以后如果需要进行修改,为了保证之前的序列化和反序列化能够兼容新的message,message的修改需要满足以下规则:
Protobuf 序列化后所生成的二进制消息非常紧凑,这得益于 Protobuf 采用的非常巧妙的 Encoding 方法。接下来看一看Protobuf协议是如何实现高效编码的。
之前已经做过描述,Protobuf的message中有很多字段,每个字段的格式为:**修饰符 字段类型 字段名 = 域号; **
Varint是一种紧凑的表示数字的方法。它用一个或多个字节来表示一个数字,值越小的数字使用越少的字节数。这能减少用来表示数字的字节数。
Varint中的每个byte的最高位bit有特殊的含义,如果该位为1,表示后续的byte也是该数字的一部分,如果该位为0,则结束。其他的7个bit都用来表示数字。因此小于128的数字都可以用一个byte表示。大于128的数字,比如300,会用两个字节来表示:1010 1100 0000 0010。下图演示了 Google Protocol Buffer 如何解析两个bytes。注意到最终计算前将两个byte的位置相互交换过一次,这是因为 Google Protocol Buffer 字节序采用little-endian的方式。 [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-lGbuIxty-1595164283892)(https://github.com/rubyhan1314/Golang-100-Days/raw/master/Day77(protobuf)]/img/121732zuyzkxzxjkwwjkx5.jpg)
在序列化时,Protobuf按照TLV的格式序列化每一个字段,T即Tag,也叫Key;V是该字段对应的值value;L是Value的长度,如果一个字段是整形,这个L部分会省略。
序列化后的Value是按原样保存到字符串或者文件中,Key按照一定的转换条件保存起来,序列化后的结果就是 KeyValueKeyValue…依次类推的样式,示意图如下所示: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JQ8HxazK-1595164283894)(https://github.com/rubyhan1314/Golang-100-Days/raw/master/Day77(protobuf)]/img/121758l5mln3mnvpvc4mzw.jpg)
采用这种Key-Pair结构无需使用分隔符来分割不同的Field。对于可选的Field,如果消息中不存在该field,那么在最终的Message Buffer中就没有该field,这些特性都有助于节约消息本身的大小。比如,我们有消息order1:
Order.id = 10;
Order.desc = "bill";
则最终的 Message Buffer 中有两个Key-Value对,一个对应消息中的id;另一个对应desc。Key用来标识具体的field,在解包的时候,Protocol Buffer根据Key就可以知道相应的Value应该对应于消息中的哪一个field。
Key 的定义如下:
(field_number << 3) | wire_type
可以看到 Key 由两部分组成。第一部分是 field_number,比如消息lm.helloworld中field id 的field_number为1。第二部分为wire_type。表示 Value的传输类型。而wire_type有以下几种类型:
go get https://github.com/protocolbuffers/protobuf-go
gopath/bin 下才可以
message xxx {
// 字段规则:required -> 字段只能也必须出现 1 次
// 字段规则:optional -> 字段可出现 0 次或1次
// 字段规则:repeated -> 字段可出现任意多次(包括 0)
// 类型:int32、int64、sint32、sint64、string、32-bit ....
// 字段编号:0 ~ 536870911(除去 19000 到 19999 之间的数字)
字段规则 类型 名称 = 字段编号;
}
//proto版本
syntax="proto3"
package message;
//请求参数
message OrderRequest {
string OrderId = 1;
int64 timeStap = 2;
}
message Info {
string OrderId = 1;
string OrderName = 2;
string OrderStatus = 3;
}
编译文件路径 编译后存储位置
protoc ./message.proto --go_out=./
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Knj0haV9-1595164283894)(grpc.assets/1588579859571.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vDiLfzzL-1595164283895)(grpc.assets/1588583162897.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CTyOyzCG-1595164283895)(grpc.assets/1588583065608.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-osLJQDJt-1595164283896)(grpc.assets/1588583268175.png)]
官网
https://grpc.io
源码
http://github.com/grpc/grpc
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EcmM9gjd-1595164283896)(grpc.assets/1588583596487.png)]
安装
第一种
go get -u https://github.com/grpc/grpc-go
第二种
git clone xxxxx
解压
改名
gprc-go
放到src/google.goland.org/grpc 改下名
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Z9kl8dHQ-1595164283897)(grpc.assets/1588591131317.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vWzMEuBW-1595164283898)(grpc.assets/1588591183845.png)]