您现在的位置是:网站首页> 编程资料编程资料
Go语言程序开发gRPC服务_Golang_
2023-05-26
554人已围观
简介 Go语言程序开发gRPC服务_Golang_
前言
gRPC 这项技术真是太棒了,接口约束严格,性能还高,在 k8s 和很多微服务框架中都有应用。
作为一名程序员,学就对了。
之前用 Python 写过一些 gRPC 服务,现在准备用 Go 来感受一下原汁原味的 gRPC 程序开发。
本文的特点是直接用代码说话,通过开箱即用的完整代码,来介绍 gRPC 的各种使用方法。
代码已经上传到 GitHub,下面正式开始。
介绍

gRPC 是 Google 公司基于 Protobuf 开发的跨语言的开源 RPC 框架。gRPC 基于 HTTP/2 协议设计,可以基于一个 HTTP/2 链接提供多个服务,对于移动设备更加友好。
入门
首先来看一个最简单的 gRPC 服务,第一步是定义 proto 文件,因为 gRPC 也是 C/S 架构,这一步相当于明确接口规范。
proto
syntax = "proto3"; package proto; // 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; } 使用 protoc-gen-go 内置的 gRPC 插件生成 gRPC 代码:
protoc --go_out=plugins=grpc:. helloworld.proto
执行完这个命令之后,会在当前目录生成一个 helloworld.pb.go 文件,文件中分别定义了服务端和客户端的接口:
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://godoc.org/google.golang.org/grpc#ClientConn.NewStream. type GreeterClient interface { // Sends a greeting SayHello(ctx context.Context, in *HelloRequest, opts ...grpc.CallOption) (*HelloReply, error) } // GreeterServer is the server API for Greeter service. type GreeterServer interface { // Sends a greeting SayHello(context.Context, *HelloRequest) (*HelloReply, error) } 接下来就是写服务端和客户端的代码,分别实现对应的接口。
server
package main import ( "context" "fmt" "grpc-server/proto" "log" "net" "google.golang.org/grpc" "google.golang.org/grpc/reflection" ) type greeter struct { } func (*greeter) SayHello(ctx context.Context, req *proto.HelloRequest) (*proto.HelloReply, error) { fmt.Println(req) reply := &proto.HelloReply{Message: "hello"} return reply, nil } func main() { lis, err := net.Listen("tcp", ":50051") if err != nil { log.Fatalf("failed to listen: %v", err) } server := grpc.NewServer() // 注册 grpcurl 所需的 reflection 服务 reflection.Register(server) // 注册业务服务 proto.RegisterGreeterServer(server, &greeter{}) fmt.Println("grpc server start ...") if err := server.Serve(lis); err != nil { log.Fatalf("failed to serve: %v", err) } } client
package main import ( "context" "fmt" "grpc-client/proto" "log" "google.golang.org/grpc" ) func main() { conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure()) if err != nil { log.Fatal(err) } defer conn.Close() client := proto.NewGreeterClient(conn) reply, err := client.SayHello(context.Background(), &proto.HelloRequest{Name: "zhangsan"}) if err != nil { log.Fatal(err) } fmt.Println(reply.Message) } 这样就完成了最基础的 gRPC 服务的开发,接下来我们就在这个「基础模板」上不断丰富,学习更多特性。
流方式
接下来看看流的方式,顾名思义,数据可以源源不断的发送和接收。
流的话分单向流和双向流,这里我们直接通过双向流来举例。
proto
service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply) {} // Sends stream message rpc SayHelloStream (stream HelloRequest) returns (stream HelloReply) {} } 增加一个流函数 SayHelloStream,通过 stream 关键词来指定流特性。
需要重新生成 helloworld.pb.go 文件,这里不再多说。
server
func (*greeter) SayHelloStream(stream proto.Greeter_SayHelloStreamServer) error { for { args, err := stream.Recv() if err != nil { if err == io.EOF { return nil } return err } fmt.Println("Recv: " + args.Name) reply := &proto.HelloReply{Message: "hi " + args.Name} err = stream.Send(reply) if err != nil { return err } } } 在「基础模板」上增加 SayHelloStream 函数,其他都不需要变。
client
client := proto.NewGreeterClient(conn) // 流处理 stream, err := client.SayHelloStream(context.Background()) if err != nil { log.Fatal(err) } // 发送消息 go func() { for { if err := stream.Send(&proto.HelloRequest{Name: "zhangsan"}); err != nil { log.Fatal(err) } time.Sleep(time.Second) } }() // 接收消息 for { reply, err := stream.Recv() if err != nil { if err == io.EOF { break } log.Fatal(err) } fmt.Println(reply.Message) } 通过一个 goroutine 发送消息,主程序的 for 循环接收消息。
执行程序会发现,服务端和客户端都不断有打印输出。
验证器
接下来是验证器,这个需求是很自然会想到的,因为涉及到接口之间的请求,那么对参数进行适当的校验是很有必要的。
在这里我们使用 protoc-gen-govalidators 和 go-grpc-middleware 来实现。
先安装:
go get github.com/mwitkow/go-proto-validators/protoc-gen-govalidators go get github.com/grpc-ecosystem/go-grpc-middleware
接下来修改 proto 文件:
proto
import "github.com/mwitkow/go-proto-validators@v0.3.2/validator.proto"; message HelloRequest { string name = 1 [ (validator.field) = {regex: "^[z]{2,5}$"} ]; } 在这里对 name 参数进行校验,需要符合正则的要求才可以正常请求。
还有其他验证规则,比如对数字大小进行验证等,这里不做过多介绍。
接下来生成 *.pb.go 文件:
protoc \ --proto_path=${GOPATH}/pkg/mod \ --proto_path=${GOPATH}/pkg/mod/github.com/gogo/protobuf@v1.3.2 \ --proto_path=. \ --govalidators_out=. --go_out=plugins=grpc:.\ *.proto 执行成功之后,目录下会多一个 helloworld.validator.pb.go 文件。
这里需要特别注意一下,使用之前的简单命令是不行的,需要使用多个 proto_path 参数指定导入 proto 文件的目录。
官方给了两种依赖情况,一个是 google protobuf,一个是 gogo protobuf。我这里使用的是第二种。
即使使用上面的命令,也有可能会遇到这个报错:
Import "github.com/mwitkow/go-proto-validators/validator.proto" was not found or had errors
但不要慌,大概率是引用路径的问题,一定要看好自己的安装版本,以及在 GOPATH 中的具体路径。
最后是服务端代码改造:
引入包:
grpc_middleware "github.com/grpc-ecosystem/go-grpc-middleware" grpc_validator "github.com/grpc-ecosystem/go-grpc-middleware/validator"
然后在初始化的时候增加验证器功能:
server := grpc.NewServer( grpc.UnaryInterceptor( grpc_middleware.ChainUnaryServer( grpc_validator.UnaryServerInterceptor(), ), ), grpc.StreamInterceptor( grpc_middleware.ChainStreamServer( grpc_validator.StreamServerInterceptor(), ), ), )
启动程序之后,我们再用之前的客户端代码来请求,会收到报错:
2021/10/11 18:32:59 rpc error: code = InvalidArgument desc = invalid field Name: value 'zhangsan' must be a string conforming to regex "^[z]{2,5}$" exit status 1 因为 name: zhangsan 是不符合服务端正则要求的,但是如果传参 name: zzz,就可以正常返回了。
Token 认证
终于到认证环节了,先看 Token 认证方式,然后再介绍证书认证。
先改造服务端,有了上文验证器的经验,那么可以采用同样的方式,写一个拦截器,然后在初始化 server 时候注入。
认证函数:
func Auth(ctx context.Context) error { md, ok := metadata.FromIncomingContext(ctx) if !ok { return fmt.Errorf("missing credentials") } var user string var password string if val, ok := md["user"]; ok { user = val[0] } if val, ok := md["password"]; ok { password = val[0] } if user != "admin" || password != "admin" { return grpc.Errorf(codes.Unauthenticat
点击排行
本栏推荐
