在掌握rpc、gRPC和proto的一些基本概念、可以书写简单(看懂)proto文件、可以编译proto文件生成对应语言文件后(有点类似SOAP协议需要SWDL文档,proto文档可以结合json并且proto文件书写起来稍微简单一些),我们结合实例进行快速使用,看下如何序列化反序列化需要传递的数据以及如何快速生成rpc服务及如何进行多语言交互。
我们这里以Go语言生成rpc服务,然后使用go客户端做测试,理论上还可以使用c++、Java、python等进行测试,等后面有机会用到的话再单独出实例分析总结。
其它语言的一些支持需要的可以在这里查看并结合Quick Start的文档自行做下测试:https://www.grpc.io/docs/languages
我们这里仿照官方的hello world实例写一个简单的rpc服务提供获取服务版本号的功能,以此总结一下从proto文件到编译生成go文件,到利用生成的go文件创建服务端程序和客户端程序这个流程做一下总结,过程中会对注意事项和踩坑点做详细总结便于下次快速搭建。
这个其实也是编程的一部分,proto是一个简单的语言,利用该语言和我们需要设计的接口写.proto文件。
关于proto的语法和规范我们之前已经总结了,这里就不多说了,建议先看这部分对相关概念有了解和学习。
syntax = "proto3";
option go_package = "./;test_grpc";
package test_grpc;
service TestGRPC {
rpc GetVersion (GetVersionRequest) returns (GetVersionResponse);
}
message BaseResponse {
int32 code = 1; // 错误码 0:成功; 其他:参考《错误码统计》
string message = 2; // 错误消息
}
/*
** 版本信息
*/
message Version {
string software_version = 1; // 软件版本号
int64 last_compile_time = 2; // 上次编译时间
}
message GetVersionRequest {
}
message GetVersionResponse {
BaseResponse response = 1;
Version version = 2;
}
其实官方的go插件安装讲的比较笼统,我就着了这个道,通过官方的文档我们可以知道除了要安装protoc之外,还需要安装protoc-gen-go,实际上如果我们需要使用Go生成rpc服务接口的话还需要安装protoc-gen-go-grpc插件,否则只会生成一个.pb.go,我们在进行rpc服务开发时还需要xx_grpc.pb.go,官方的指南中很容易忽略这一点。
注:官方是将proto和gRPC可能是为了强调proto和gRPC的独立性,所以只说了protoc-gen-go插件,实际上当结合go+gRPC+proto使用时需要安装一个软件,两个插件:
1、protoc
2、protoc-gen-go-grpc
3、protoc-gen-go
因为rpc的框架不止gRPC,proto也可以和其它rpc框架结合使用。
1.安装protoc
根据操作系统具体安装:https://github.com/protocolbuffers/protobuf/tags
2.安装protoc-gen-go
go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
3.安装protoc-gen-go-rpc
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@latest
需要指定两个插件以及源文件的位置和生成文件的位置,可以指定相对路径(可通过protoc --help获取帮助):
protoc -I=. --go_out=. --go-grpc_out=. ./version.proto
根据proto文件会生成两个文件,这个是官方例子的文件树:
D:\mynotes\go\src\google.golang.org\grpc\examples\helloworld>tree . /F
文件夹 PATH 列表
卷序列号为 0865-F52B
D:\MYNOTES\GO\SRC\GOOGLE.GOLANG.ORG\GRPC\EXAMPLES\HELLOWORLD
├─greeter_client
│ main.go
│
├─greeter_server
│ main.go
│
└─helloworld
helloworld.pb.go
helloworld.proto
helloworld_grpc.pb.go
这个是我们生成的代码的文件:
$ ls
version.pb.go version.proto version_grpc.pb.go
生成.pb.go等插件生成的文件后在使用时需要一些依赖(protobuf相关包及其依赖),但是这些依赖我通过go get的一些全球代理或者七牛的国内代理也没有获取成功,最后还是采用老方法,直接通过GitHub上的仓库clone下载(或者fork到gitee等国内仓后下载,避免某些时候墙的厉害GitHub也无法访问下载),比如:
git clone https://github.com/golang/net.git src/golang.org/x/net
git clone https://github.com/golang/crypto.git src/golang.org/x/crypto
git clone https://github.com/golang/sys.git src/golang.org/x/sys
git clone https://github.com/golang/text.git src/golang.org/x/text
git clone https://github.com/googleapis/go-genproto.git src/google.golang.org/genproto
其它有需要的话也是类似的处理方式。
直接上代码吧,结合官方的helloworld实例,如果前面编译proto文件及依赖安装没有问题的话照猫画虎即可:
package main
import (
"context"
"gitee.com/commanderZY/test_grpc/version"
"google.golang.org/grpc"
"log"
"net"
)
type server struct {
test_grpc.UnimplementedTestGRPCServer
}
func (s *server) GetVersion(ctx context.Context, in *test_grpc.GetVersionRequest) (*test_grpc.GetVersionResponse, error) {
resp := test_grpc.BaseResponse{
Code: 0,
Message: "",
}
ver := test_grpc.Version{
SoftwareVersion: "1.0",
LastCompileTime: 0,
}
verResp := test_grpc.GetVersionResponse{
Response: &resp,
Version: &ver,
}
return &verResp, nil
}
func main() {
lis, err := net.Listen("tcp", ":50051")
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
test_grpc.RegisterTestGRPCServer(s, &server{
})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
无法将 ‘&server{}’ (类型 *server) 用作类型 TestGRPCServer 类型未实现 ‘TestGRPCServer’ ,因为缺少某些方法: mustEmbedUnimplementedTestGRPCServer()
在issue上找到了解决方法:
https://github.com/grpc/grpc-go/issues/3794
即在定义的结构体中嵌入该空方法即可。
代码:
package main
import (
"context"
"fmt"
testgrpc "gitee.com/commanderZY/test_grpc/version"
"google.golang.org/grpc"
)
func main() {
conn, err := grpc.Dial(":50051", grpc.WithInsecure())
if err != nil {
fmt.Println("connect server failed,", err)
}
defer conn.Close()
c := testgrpc.NewTestGRPCClient(conn)
r1, err := c.GetVersion(context.Background(), &testgrpc.GetVersionRequest{
})
if err != nil {
fmt.Println("can't get version,", err)
return
}
fmt.Println(r1.Version)
}
先编译运行服务端程序,然后运行客户端程序,测试结果如下: