4. 基于Protobuf的RPC(可跳过这部分)
对于没有⽤过Protobuf的读者,建议先从官⽹了解下基本⽤法。这⾥我们尝试将Protobuf和RPC结合在 ⼀起使⽤,通过Protobuf来最终保证RPC的接⼝规范和安全。Protobuf中最基本的数据单元是 message,是类似Go语⾔中结构体的存在。在message中可以嵌套message或其它的基础数据类型的 成员。
定义RPC数据结构:
07-pbrpc/service/service.proto
syntax = "proto3"; package hello; // go module = MicroServiceStudy01 option go_package = "MicroServiceStudy01/07-pbrpc/service"; message Request{ string value = 1; } message Response{ string value = 1; }
生成go语言结构:
$ cd 07-pbrpc $ protoc -I ./service --go_out=./service --go_opt=module="MicroServiceStudy01/07-pbrpc/service" service/service.prot o
定义RPC接口:
基于 生成的数据结构,定义接口:
07-pbrpc/service/interface.go
package service const HelloServiceName = "HelloService" type HelloService interface { // Hello // 这里的 Request 和 Response 是基于protobuf生成的service.pb.go里的结构 Hello(request *Request, response *Response) error }
这个接口时为了约束参数,详见2.更安全的RPC接口
向之前没有联合protobuf使用的时候,我们这里的接口方法的参数类型是我们自己写的结构体类型,而使用了protobuf之后,这里的参数类型就需要引用我们通过protobuf生成的.pb.go文件里的结构体类型。
我们定义的接口要放在一个 独立的文件里类似于当前的service包,他就相当于一个契约包,用来 约束服务端server(提供RPC服务)和我们的客户端client(调用RPC服务)。
定义服务端:
07-pbrpc/server/server.go
type HelloService struct{} func (hs *HelloService) Hello(req *service.Request, resp *service.Response) error { resp.Value = "hello:" + req.Value return nil } // 通过接口约束 Server 端 var _ service.HelloService = (*HelloService)(nil) func main() { rpc.RegisterName(service.HelloServiceName, new(HelloService)) listen, err := net.Listen("tcp", ":1234") if err != nil { log.Fatal("Listen TCP err:", err) } for { conn, err := listen.Accept() if err != nil { log.Fatal("Accept err:", err) } // 这里使用的还是json,先忽略 往下看 go rpc.ServeCodec(jsonrpc.NewServerCodec(conn)) } }
定义客户端:
07-pbrpc/client/client.go
type HelloServiceClient struct { *rpc.Client } func (hsc HelloServiceClient) Hello(req *service.Request, resp *service.Response) error { return hsc.Client.Call(service.HelloServiceName+".Hello", req, resp) } // 通过接口约束 Client 端 var _ service.HelloService = (*HelloServiceClient)(nil) func DialHelloService(network, address string) (*HelloServiceClient, error) { conn, err := net.Dial(network, address) if err != nil { log.Fatal("net.Dail err: ", err) } client := rpc.NewClientWithCodec(jsonrpc.NewClientCodec(conn)) return &HelloServiceClient{client}, nil } func main() { client, err := DialHelloService("tcp", "localhost:1234") if err != nil { log.Fatal("Dial err: ", err) } resp := &service.Response{} err = client.Hello(&service.Request{Value: "world"}, resp) if err != nil { log.Fatal(err) } fmt.Println(resp) }
此时我们只是Hello方法的参数使用的是protobuf生成的service.pb.go中的结构体,但是其他逻辑依然没有改变,使用的还是json-rpc,所以这里会发现,我们这次虽然定义了相关的protobuf,但是我们和protobuf还没有半毛钱关系,只是用到了他为我们生成的结构体;
那么我们如何将json编码换成protobuf编码呢?
将07-pbrpc/server/server.go里的go rpc.ServeCodec(jsonrpc.NewServerCodec(conn))修改成go rpc.ServeCodec(server.NewServerCodec(conn))即可。
这是我们4.基于Protobuf的RPC的重点,官方的net/rpc包里是没有protoc的插件
我看的视频的发布者仿照net/rpc/jsonrpc自己写了个关于Proto Codec 编解码的包,但是视频中没有放出来,而这里的NewServerCodec就用到了那个包里的方法,大家不用深究,逻辑就是这么个逻辑,重在理解。