学学 grpc-go,顺便整整优化
简单应答服务 安装idl转换工具
1 2 go install google.golang.org/protobuf/cmd/protoc-gen-go go install google.golang.org/grpc/cmd/protoc-gen-go-grpc
然后写一份proto
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 syntax = "proto3" ; option go_package = "grpcserver/repo" ;message Request { string msg = 1 ; } message Response { string msg = 1 ; } service Greeter { rpc Echo(Request) returns (Response) {} }
编写完进入代码路径,执行
1 protoc.exe --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative .\server.proto
就可以生成 *.pb.go
和 *_grpc.pb.go
两个源码文件,接下来实现相关方法。
1 2 3 4 5 6 7 8 9 10 11 12 import ( "context" "grpcserver/repo" ) type GreeterImpl struct { repo.UnimplementedGreeterServer } func (*GreeterImpl) Echo(ctx context.Context, in *repo.Request) (*repo.Response, error ) { return &repo.Response{Msg: in.Msg}, nil }
实现加入的 repo.UnimplementedGreeterServer
,是grpc-go后面版本新增加的,默认实现了所有接口的对象。默认方法是返回一个错误,显示接口未实现,保证主调方用新pb调用服务端旧协议时,出现奇怪的错误。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 type GreeterServer interface { Echo(context.Context, *Request) (*Response, error ) mustEmbedUnimplementedGreeterServer() } type UnimplementedGreeterServer struct {} func (UnimplementedGreeterServer) Echo(context.Context, *Request) (*Response, error ) { return nil , status.Errorf(codes.Unimplemented, "method Echo not implemented" ) } func (UnimplementedGreeterServer) mustEmbedUnimplementedGreeterServer() {}
接下来就是监听并服务了。grpc有两种监听方式,一种是直接listen一个tcp socket,另外一种是通过 ServeHTTP
,和http server 一起工作。下面是直接listen tcp的实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 package mainimport ( "grpcserver/repo" "grpcserver/service" "net" "google.golang.org/grpc" ) func main () { sock, err := net.Listen("tcp" , "127.0.0.1:9999" ) if err != nil { panic (err) } server := grpc.NewServer() repo.RegisterGreeterServer(server, &service.GreeterImpl{}) if err := server.Serve(sock); err != nil { panic (err) } }
客户端
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 package mainimport ( "context" "fmt" "grpcserver/repo" "google.golang.org/grpc" ) func main () { conn, err := grpc.Dial("127.0.0.1:9999" , grpc.WithInsecure()) if err != nil { panic (err) } defer conn.Close() client := repo.NewGreeterClient(conn) resp, err := client.Echo(context.Background(), &repo.Request{ Msg: "hello" , }) if err != nil { panic (err) } fmt.Println(resp) }
到这里,一个简单的 Echo 服务就搭好了。
这里不难看出,每次都是Dial一个新的链接,然后发起请求,并没有复用链接,所以其实有优化空间。
流式服务 在pb的 request 和 response 前面加个 stream ,就是流接口定义
1 2 3 4 service Greeter { rpc Echo(Request) returns (Response) {} rpc StreamEcho(stream Request) returns (stream Response) {} }
服务端新增个接口实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 func (*GreeterImpl) StreamEcho(s repo.Greeter_StreamEchoServer) error { var wg sync.WaitGroup wg.Add(1 ) go func () { for i := 0 ; i < 10 ; i++ { time.Sleep(time.Second) s.Send(&repo.Response{ Msg: fmt.Sprintf("hello %d" , i), }) } wg.Done() }() wg.Wait() return nil }
client 写个循环 recv
1 2 3 4 5 6 7 8 9 10 11 c, err := client.StreamEcho(context.Background()) if err != nil { panic (err) } for { msg, err := c.Recv() if err != nil { break } fmt.Println(msg) }
配合nginx 一般会用nginx或其他工具作为中间层,tls加密放在中间层,所以grpc服务这边可以使用非加密的h2c提供服务
1 2 3 4 5 6 7 8 9 func main () { s := grpc.NewServer() repo.RegisterGreeterServer(s, &service.GreeterImpl{}) handlerFunc := http.HandlerFunc(s.ServeHTTP) handler := h2c.NewHandler(handlerFunc, &http2.Server{}) http.ListenAndServe("127.0.0.1:9999" , handler) }