官方grpc小例子分析
目录结构
helloword.proto
client、server交互的接口定义文件,protoc命令会解析此文件生成对应的接口文件。它就好比是电话簿,记录名字(可以理解为函数名)和它的电话号码(可以理解路由到server具体方法的地址),client就可以根据它来调用server的特定方法。
syntax = "proto3";
option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
package helloworld;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
SayHello是client、server通信的函数,server端定义此函数,client端与server端建立连接后可以直接调用此函数
greeter_server/main.go
package main
import (
"context"
"log"
"net"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
const (
port = ":50051"
)
// server is used to implement helloworld.GreeterServer.
type server struct {
pb.UnimplementedGreeterServer
}
// SayHello implements helloworld.GreeterServer
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Printf("Received: %v", in.GetName())
return &pb.HelloReply{Message: "Hello " + in.GetName()}, nil
}
func main() {
lis, err := net.Listen("tcp", port)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
if err := s.Serve(lis); err != nil {
log.Fatalf("failed to serve: %v", err)
}
}
接口函数SayHello具体的逻辑在server层定义
- server结构体继承grpc文件中的结构体UnimplementedGreeterServer (绑定了SayHello方法)
- 对server结构体重新定义Sayhello接口
- 客户端与server建连后可以直接调用服务层的SayHello方法
greeter_client/main.go
package main
import (
"context"
"log"
"os"
"time"
"google.golang.org/grpc"
pb "google.golang.org/grpc/examples/helloworld/helloworld"
)
const (
address = "localhost:50051"
defaultName = "world"
)
func main() {
// Set up a connection to the server.
conn, err := grpc.Dial(address, grpc.WithInsecure(), grpc.WithBlock())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn)
// Contact the server and print out its response.
name := defaultName
if len(os.Args) > 1 {
name = os.Args[1]
}
ctx, cancel := context.WithTimeout(context.Background(), time.Second)
defer cancel()
r, err := c.SayHello(ctx, &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())
}
- 调用grpc.dial方法与server建立连接
- 调用server层的接口函数,这里的调用方式:c.Sayhello
准备环境
- 安装protoc命令的方式
- 依赖protocol buffer 编译器
protoc 默认不支持go语言,需要下载安装go插件
# 下载安装包到$GOPATH/google.golang.org/ 下,因为待安装的包导入路径是google.golang.org
git clone https://github.com/protocolbuffers/protobuf-go.git
git clone https://github.com/grpc/grpc-go.git
# 安装,安装完之后生产全局可见的二进制可执行文件
git install google.golang.org/protobuf/cmd/protoc-gen-go
go install google.golang.org/grpc-go/cmd/protoc-gen-go-grpc
编辑protoc文件注意项
- 文件头部加上go_package选项,如
syntax = "proto3"; f
option go_package = "google.golang.org/grpc/examples/helloworld/helloworld";
option java_multiple_files = true;
option java_package = "io.grpc.examples.helloworld";
option java_outer_classname = "HelloWorldProto";
package helloworld;
- 如果引用了其他protoc文件中的变量则导入相关文件,如
syntax = "proto3";
// redis-agent在$GOPATH/src下
option go_package = "redis-agent/vendor/tinker/communicator/protobuf";
package protobuf;
import "tinker.proto";
进入protoc文件所在的目录,编译protoc文件,会在当前目录下生成对应的go文件
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proxy_agent.proto
- 确保protoc和grpc包版本对应,否则protoc编译出的$前缀_grpc.go文件可能会遇到方法或变量未定义的问题,如
我这里部署采用的版本如下,下载的都是最新版本的主分支
$ protoc-gen-go --version
protoc-gen-go v1.25.0-devel
$ protoc-gen-go-grpc --version
protoc-gen-go-grpc 1.0.1
$ protoc --version
libprotoc 3.11.4
遇到的问题
- 调用rpc接口的server文件报异常
报错意思是rpc注册函数agentpb.RegisterProxyAgentServer中的第二个结构体参数定义了mustEmbedUnimplementedProxyAgentServer方法,但是传的&ProxyAgentServer{}参数却没有绑定这种方法。agentpb文件中UnimplementedProxyAgentServer结构体绑定了这种方法
所以在server文件中可以通过继承UnimplementedProxyAgentServer结构体的方式来解决,如
# server 文件中ProxyAgentServer结构体继承agentpb.UnimplementedProxyAgentServer
type ProxyAgentServer struct {
agentpb.UnimplementedProxyAgentServer
}