|
gRPC(google+remote process call) 详解
grpc 简介
grpc是一个高性能、开源和通用的 RPC 框架,面向移动和 HTTP/2 设计。所谓RPC(remote procedure call 远程过程调用)框架实际是提供了一套机制,使得应用程序之间可以进行通信,而且也遵从server/client模型。使用的时候客户端调用server端提供的接口就像是调用本地的函数一样。如下图所示就是一个典型的RPC结构图。
[图片上传失败...(image-861e7e-1649389684397)]
客户端使用RPC调用远程方法,客户端的存根发起请求,对请求的数据使用protobuf对象序列化和压缩。服务器接收到请求后,解码请求数据,使用protobuf反序列化为内存对象,处理业务逻辑并返回响应结果。客户端收到服务端响应,解码响应数据,唤醒阻塞客户端。
grpc 使用
工欲善其事必先利其器,开发一个grpc示例之前先安装好需要的工具和插件
下载protoc工具 https://github.com/protocolbuffers/protobuf/releases/ 注意系统和版本, 下载完成后,执行路径添加到环境变量
插件安装
go get -u github.com/golang/protobuf/protoc-gen-go, GOBIN($GOPATH/bin)添加到环境变量即可1
grpc 示例
syntax = "proto3";package pb;option go_package = "./;pb";message Message { string body = 1;}message ReqMsgNum{}message RespMsgNum{ int32 num = 1;}message ReqMatchWord{ string word = 1;}message RespMatchWord{ bool isMatch = 1;}service ChatService { rpc SayHello(Message) returns (Message) {} rpc GetMsgNum(ReqMsgNum) returns (stream RespMsgNum){} rpc MatchWord(stream ReqMatchWord) returns (RespMatchWord){} rpc Chat(stream Message) returns (stream Message){}}
这里定义了四个方法
sayHello 普通的rpc调用
GetMsgNum 服务端流式调用,客户端普通的rpc调用
MatchWord 客户端流式调用,服务端普通rcp调用
Chat 双向流式调用
protoc --go_out=plugins=grpc:. chat.protopackage mainimport ( "google.golang.org/grpc" "log" "mytest/grpc/pb" "mytest/grpc/server/chat" "net")func main() { listen, err := net.Listen("tcp", ":1234") if err != nil { log.Fatal(err) } s := chat.ChatServer{} rpcServer := grpc.NewServer() pb.RegisterChatServiceServer(rpcServer, &s) log.Println("start server") if err := rpcServer.Serve(listen); err != nil{ log.Fatal(err) }}package chatimport ( "context" "log" "mytest/grpc/pb" "strings" "time")type ChatServer struct {}// 普通请求func (s *ChatServer) SayHello(ctx context.Context, in *pb.Message) (*pb.Message, error) { log.Println("Receive message => ", in.Body) return &pb.Message{Body: "Hello From the Server!"}, nil}// 服务端流推送消息数量func (s *ChatServer) GetMsgTotal(in *pb.ReqMsgNum, stream pb.ChatService_GetMsgNumServer) error { var msgAmount int32 = 0 for { msgAmount++ err := stream.Send(&pb.RespMsgNum{Num: msgAmount}) if err != nil { log.Println(err) break } time.Sleep(time.Second) } return nil}// 服务端接收客户端流func (s *ChatServer) MatchWord(stream pb.ChatService_MatchWordServer) error { var matchAmount int32 for { resp, err := stream.Recv() if err != nil { log.Println(err) break } log.Println("recv word => ", resp.Word) // TODO 业务处理 是否匹配单词 if strings.Contains(resp.Word, "good") { matchAmount++ } } err := stream.SendAndClose(&pb.RespMatchWord{IsMatch: matchAmount>0}) if err != nil{ log.Println(err) return err } return nil}// 双向流通信func (s *ChatServer) Chat(stream pb.ChatService_ChatServer) error { var msg string for { resp, err := stream.Recv() if err != nil{ log.Println(err) break } log.Println("recv msg => ", resp.Body) msg = "server " + resp.Body err = stream.Send(&pb.Message{Body: msg}) if err != nil{ log.Println(err) break } log.Println("send msg => ", msg) } return nil}package mainimport ( "context" "fmt" "google.golang.org/grpc" "log" "mytest/grpc/pb" "time")var ctx = context.Background()func main() { conn, err := grpc.Dial(":1234", grpc.WithInsecure()) if err != nil{ log.Fatal(err) } defer conn.Close() log.Println("start client") cli := pb.NewChatServiceClient(conn) sayHello(cli) //getMsgTotal(cli) //sendWord(cli) //chat(cli)}func sayHello(cli pb.ChatServiceClient) { resp, err := cli.SayHello(ctx, &pb.Message{Body: "i am tom"}) if err != nil{ log.Fatal(err) } log.Println(resp.Body)}// 服务端流的方式推送消息func getMsgNum(cli pb.ChatServiceClient) { stream, err := cli.GetMsgNum(ctx, &pb.ReqMsgNum{}) if err != nil{ log.Fatal(err) } for { resp, err := stream.Recv() if err != nil{ log.Println(err) break } log.Println("msg total => ", resp.Num) }}// 客户端流的方式发送消息func sendWord(cli pb.ChatServiceClient){ stream, err := cli.MatchWord(ctx) if err != nil{ log.Fatal(err) } var sendStr string = "abc" for i := 0; i < 5; i++{ log.Println("send str => ", sendStr) err := stream.Send(&pb.ReqMatchWord{Word: sendStr}) if err != nil{ log.Fatal(err) return } sendStr += "good" time.Sleep(time.Second) } resp, err := stream.CloseAndRecv() if err != nil{ log.Println(err) return } log.Println("the word match => ", resp.IsMatch)}// 双向流收发消息func chat(cli pb.ChatServiceClient) { stream, err := cli.Chat(ctx) if err != nil{ log.Fatal(err) } var i int var msg string for i < 100 { msg = fmt.Sprintf("chat msg %d", i) fmt.Println("send msg => ", msg) err := stream.Send(&pb.Message{Body: msg}) if err != nil{ fmt.Println(err) break } resp, err := stream.Recv() if err != nil{ fmt.Println(err) break } log.Println("recv msg => ", resp.Body) time.Sleep(time.Second * 2) i++ }}遇到问题:
1. 使用protoc生成go文件提示 protoc-gen-go: unable to determine Go import path,
该问题出现原因是没有正确设置option go_package。在proto文件中添加option go_package = "./;pb";其中pb是包名,可以自定义, ./表示当前目录。该选项主要是用于配置包依赖路径,例如a.proto imports b.proto,则生成的pd.go文件也有依赖关系,因此要设置该路径。
2. 运行时提示
undefined: grpc.SupportPackageIsVersion7undefined: grpc.ClientConnInterfaceundefined: grpc.ClientConnInterfaceundefined: grpc.ServiceRegistrar
出现这个是因为 grpc和proto-gen-go 的版本问题导致的,两种解决方案。
升级grpc版本1.27或以上,笔者这里升级到1.29.1
replace google.golang.org/grpc => google.golang.org/grpc v1.29.1go get -u github.com/golang/protobuf/protoc-gen-go是安装最新版的protoc-gen-go降低protoc-gen-go的具体办法,在终端运行如下命令,这里降低到版本 v1.2.0GIT_TAG="v1.2.0"go get -d -u github.com/golang/protobuf/protoc-gen-gogit -C "$(go env GOPATH)"/src/github.com/golang/protobuf checkout $GIT_TAGgo install github.com/golang/protobuf/protoc-gen-go3. proto 请求参数或者响应参数为空
通过引入 empty.proto,具体看示例
syntax = "proto3";import "google/protobuf/empty.proto";package proto;message ReqHello { string message = 1;}message RespHello { string message = 1;}service Greeter { // 没有返回值 情况 rpc Hello1(ReqHello) returns (google.protobuf.Empty) {} // 没有参数 情况 rpc Hello2 (google.protobuf.Empty) returns (RespHello) {} // 没有参数,没有返回值 情况 rpc Hello3 (google.protobuf.Empty) returns (google.protobuf.Empty) {}} |
|