您现在的位置是:网站首页> 编程资料编程资料

Go语言程序开发gRPC服务_Golang_

2023-05-26 555人已围观

简介 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
                
                

-六神源码网