RPC与Protobuf(三)
Protobuf介绍
Protobuf 是Protocol Buffers 的简称,它是谷歌公司开发的一种数据描述语言, 2008开源时定位类似于XML、JSON等描述语言,通过附带的工具生成代码并实现结构化数据的功能,但我们更关注的是Protobuf作为接口规范的描述语言,可以作为设计安全的跨语言RPC接口的基础工具。
Protobuf入门
对于没有用过Protobuf的,建议先从官网了解基本用法,下面我们结合Protobuf和RPC结合一起使用,通过Protobuf来最终保证RPC的接口规范和安全。Protobuf中最基本的数据单元是message,是类似于Go语言中结构体的存在,在message中可以嵌套message或其他的基础数据类型成员。
简单实例
文件目录
---rpc_demo │ client.go │ go.mod │ go.sum │ service.go │ └─proto hello.pb.go hello.proto
首先在项目目录下创建一个proto目录用于存放proto文件,创建简单的helllo.proto文件:
syntax = "proto3"; // 声明语法 package proto; // 可根据自己当前包结构自定义 message String { // message 关键字定义了一个 String类型,且只有一个字符串类型的value成员,该成员编号为1来代替名字,这就是为什么protobuf体积小的原因,别的数据描述语言(json、xml)都是通过成员名字标识,而Portobuf通过唯一编号,不过不便于查阅 string value = 1; }
进入到proto文件目录下,使用以下命令生成相应的语言脚本:protoc --go_out=. hello.proto 生成相应的接口文件。然后我们可以根据生成的go文件,写服务端和客户端代码,先写出服务端代码。
注意:Protobuf核心代码是使用C++编写的,在官方的protobuf编译器中并不支持Go语言,要想基于hello.proto文件生成相应的go代码,我们需要下载相应的插件: go get github.com/golang/protobuf/protoc-gen-go 命令安装,最后通过protoc --go_out=. hello.proto会生成相应的go文件代码
package main import ( "log" "net" "net/rpc" "rpc_protobuf/proto" ) type HelloService struct {} // 实现rpc服务的具体方法,注意接受的参数要满足接口的要求标准,因为目前调用的都是结构体,所以采用指针的参数 func (p *HelloService) Hello (request *proto.String, reply *proto.String) error { reply.Value = "hello: " + request.GetValue() return nil } func main() { // 注册rpc服务 rpc.RegisterName("HelloService", new(HelloService)) // 监听tcp服务 listenr, err := net.Listen("tcp",":1234") if err != nil { log.Fatal("Listen error:",err) } log.Println("service starting....") // 有相关数据请求则接受,并将连挂在rpc服务上 conn, err := listenr.Accept() if err != nil { log.Fatal("Accept error",err) } rpc.ServeConn(conn) }
最后写出客户端代码:
package main import ( "fmt" "log" "net/rpc" "rpc_protobuf/proto" ) func main() { // 拨号本端1234端口服务 client, err := rpc.Dial("tcp", "localhost:1234") if err != nil { log.Fatal("TCP error:", err) } // 统一采用接口提供的参数,和请求参数 var reply = &proto.String{} var param = &proto.String{ Value: "Wang", } // 调用远程服务 err = client.Call("HelloService.Hello", ¶m, &reply) if err != nil { log.Fatal(err) } fmt.Println(reply) }
(注:在使用Goland IDE的使用,如果我们使用了go mod,在setting中的Go modules选项中的enable Go Moudle(vgo) integration 和 vendoring mode 需要调整当导包出现问题时)
我们分别启动服务端、客户端就可以查看相应的结果了,这里我们简单的使用了rpc和protobuf,之后会介绍怎么下载protoc、protobuf等
分析:
下面我们分析下生成的hello.pb.go文件(截取部分):
package proto // 包名 .... // 实现了一个字符类型,根据proto文件生成的 type String struct { Value string `protobuf:"bytes,1,opt,name=value,proto3" json:"value,omitempty"` XXX_NoUnkeyedLiteral struct{} `json:"-"` XXX_unrecognized []byte `json:"-"` XXX_sizecache int32 `json:"-"` } .... // 用于给String类型取值的,在服务可以通过这个方法进行参数的获取,每个成员都会有Get方法 func (m *String) GetValue() string { if m != nil { return m.Value } return "" }
真正的价值
上面我们已经讲解过通过复杂的操作来结合rpc和protobuf,我们使用protobuf去定义输入和输出参数以及实现的结构体,但是大家有没有发现,其实生成的hello.pb.go中是没有服务函数的只有一些接口约束,那我们能不能将RPC中的服务也定义在Protobuf中?这样我们就实现了用Protobuf定义语言无关的RPC服务接口,这才是真正的价值。
这就属于Portobuf自定制的范围了,有兴趣可以阅读《Go语言高级编程》,之前的部分文章也是摘取于这本书中。