Go语言程序开发gRPC服务

  • Post category:Python

Go语言程序开发gRPC服务攻略

什么是gRPC

gRPC是一个高性能、开源并且通用的RPC框架,由Google开源并贡献至Cloud Native Computing Foundation(CNCF)。gRPC支持超过10种语言,并且它底层使用的是HTTP2协议,以提高网络性能。

gRPC基于Protocol Buffers(proto)序列化机制,比JSON更快、更小,使用起来也更加方便。

gRPC的基础概念

  • Service:gRPC定义了一种叫做Service的概念,Service由多个方法组成。
  • Method:Service中的一个方法,可以用来接收参数、处理请求、返回结果。
  • Message:传输的数据结构,通过protobuf生成,用于在RPC过程中传输数据。

gRPC的安装

在Go语言中使用gRPC,需要安装gRPC和protobuf。

  • 安装gRPC
go get -u google.golang.org/grpc
  • 安装protobuf

首先需要安装protobuf compiler

sudo apt-get install protobuf-compiler

然后安装protobuf的Go语言支持

go get -u github.com/golang/protobuf/protoc-gen-go

基于Go语言实现gRPC服务

第一步:定义消息

gRPC客户端与服务器之间的通信是通过消息进行的。所以我们需要先定义两个消息类型,一个是表示请求消息,一个是表示响应消息。

下面是一个简单的示例,定义了一个AddRequest、AddResponse消息。

syntax = "proto3";

package calculator;

message AddRequest {
  int32 a = 1;
  int32 b = 2;
}

message AddResponse {
  int32 result = 1;
}

第二步:定义服务

接下来我们需要在.proto文件中定义服务,一个服务是多个方法的组合。

syntax = "proto3";

package calculator;

service Calculator {
  rpc Add(AddRequest) returns (AddResponse) {}
}

第三步:生成Go语言代码

定义好.proto文件后,我们需要使用protobuf编译器生成对应的Go语言代码。生成命令如下:

protoc --go_out=plugins=grpc:. calculator.proto

第四步:编写服务器代码

package main

import (
    "context"
    "net"

    "google.golang.org/grpc"
    pb "path/to/calculator"
)

type server struct{}

func (s *server) Add(ctx context.Context, req *pb.AddRequest) (*pb.AddResponse, error) {
    a := req.GetA()
    b := req.GetB()
    result := a + b

    return &pb.AddResponse{Result: result}, nil
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterCalculatorServer(s, &server{})
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

第五步:编写客户端代码

package main

import (
    "context"
    "log"

    "google.golang.org/grpc"
    pb "path/to/calculator"
)

func main() {
    conn, err := grpc.Dial(":50051", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewCalculatorClient(conn)

    req := &pb.AddRequest{A: 2, B: 3}
    res, err := c.Add(context.Background(), req)
    if err != nil {
        log.Fatalf("could not add: %v", err)
    }

    log.Printf("Result: %d", res.GetResult())
}

示例说明

以上就是一个简单的gRPC服务的搭建流程,当然这只是最简单的示例,实际情况下可能会涉及到更多的消息类型和方法,需要根据具体需求进行开发。

下面通过两个具体的例子,进一步讲解如何使用gRPC。

示例一:文件上传服务

我们需要实现一个上传文件的服务,客户端需要上传一个文件,并返回一个唯一的文件ID,服务端保存上传文件,并根据文件ID返回文件下载链接。

首先定义上传请求消息:

syntax = "proto3";

package file;

message UploadRequest {
  bytes data = 1;
}

定义上传响应消息:

syntax = "proto3";

package file;

message UploadResponse {
  string fileId = 1;
}

定义下载请求消息:

syntax = "proto3";

package file;

message DownloadRequest {
  string fileId = 1;
}

定义下载响应消息:

syntax = "proto3";

package file;

message DownloadResponse {
  bytes data = 1;
}

定义文件服务:

syntax = "proto3";

package file;

service FileService {
  rpc Upload(UploadRequest) returns (UploadResponse) {}
  rpc Download(DownloadRequest) returns (DownloadResponse) {}
}

生成Go语言代码:

protoc --go_out=plugins=grpc:. file.proto

服务端代码:

package main

import (
    "bytes"
    "context"
    "fmt"
    "io/ioutil"
    "log"
    "os"
    "path/filepath"
    "time"

    "google.golang.org/grpc"
    pb "path/to/file"
)

type server struct{}

func (s *server) Upload(ctx context.Context, in *pb.UploadRequest) (*pb.UploadResponse, error) {
    data := in.GetData()
    fileId := fmt.Sprintf("%x", time.Now().UnixNano())
    filePath := filepath.Join("./uploads/", fileId)

    if err := ioutil.WriteFile(filePath, data, os.ModePerm); err != nil {
        return nil, err
    }

    url := fmt.Sprintf("http://localhost:8080/uploads/%s", fileId)
    return &pb.UploadResponse{
        FileId: fileId,
        Url:    url,
    }, nil
}

func (s *server) Download(ctx context.Context, in *pb.DownloadRequest) (*pb.DownloadResponse, error) {
    fileId := in.GetFileId()
    filePath := filepath.Join("./uploads/", fileId)

    data, err := ioutil.ReadFile(filePath)
    if err != nil {
        return nil, err
    }

    return &pb.DownloadResponse{Data: data}, nil
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterFileServer(s, &server{})
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

客户端代码:

package main

import (
    "context"
    "fmt"
    "io/ioutil"
    "log"

    "google.golang.org/grpc"
    pb "path/to/file"
)

func main() {
    conn, err := grpc.Dial(":50051", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewFileClient(conn)

    data, err := ioutil.ReadFile("/path/to/file")
    if err != nil {
        log.Fatal(err)
    }

    uploadRes, err := c.Upload(context.Background(), &pb.UploadRequest{Data: data})
    if err != nil {
        log.Fatal(err)
    }
    fmt.Println(uploadRes.FileId)

    downloadRes, err := c.Download(context.Background(), &pb.DownloadRequest{FileId: uploadRes.FileId})
    if err != nil {
        log.Fatal(err)
    }
    ioutil.WriteFile(fmt.Sprintf("./downloads/%s", uploadRes.FileId), downloadRes.Data, 0666)
}

示例二:数据流服务

我们需要实现一个数据流服务,客户端向服务端发送数据流,服务端将收到的数据流反转并返回。

首先定义请求消息:

syntax = "proto3";

package stream;

message StreamRequest {
  string data = 1;
}

定义响应消息:

syntax = "proto3";

package stream;

message StreamResponse {
  string data = 1;
}

定义数据流服务:

syntax = "proto3";

package stream;

service StreamService {
  rpc Stream(stream StreamRequest) returns (stream StreamResponse) {}
}

生成Go语言代码:

protoc --go_out=plugins=grpc:. stream.proto

服务端代码:

package main

import (
    "context"
    "log"
    "strings"

    "google.golang.org/grpc"
    pb "path/to/stream"
)

type server struct{}

func (s *server) Stream(stream pb.StreamService_StreamServer) error {
    var data []string
    for {
        req, err := stream.Recv()
        if err != nil {
            return err
        }
        data = append(data, req.GetData())

        for i := len(data) - 1; i >= 0; i-- {
            if err := stream.Send(&pb.StreamResponse{Data: reverseString(data[i])}); err != nil {
                return err
            }
        }
    }
}

func reverseString(s string) string {
    var res string
    for _, c := range s {
        res = string(c) + res
    }
    return res
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("failed to listen: %v", err)
    }
    s := grpc.NewServer()
    pb.RegisterStreamServiceServer(s, &server{})
    if err := s.Serve(lis); err != nil {
        log.Fatalf("failed to serve: %v", err)
    }
}

客户端代码:

package main

import (
    "context"
    "io"
    "log"

    "google.golang.org/grpc"
    pb "path/to/stream"
)

func main() {
    conn, err := grpc.Dial(":50051", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("did not connect: %v", err)
    }
    defer conn.Close()
    c := pb.NewStreamServiceClient(conn)

    stream, err := c.Stream(context.Background())
    if err != nil {
        log.Fatalf("error: %v", err)
    }

    data := []string{"hello", "world", "grpc", "stream"}
    for _, d := range data {
        if err := stream.Send(&pb.StreamRequest{Data: d}); err != nil {
            log.Fatalf("error: %v", err)
        }
    }
    if err := stream.CloseSend(); err != nil {
        log.Fatalf("error: %v", err)
    }

    for {
        res, err := stream.Recv()
        if err == io.EOF {
            break
        }
        if err != nil {
            log.Fatalf("error: %v", err)
        }
        log.Println(res.GetData())
    }
}

以上就是两个具体的示例,希望读者通过这些示例更好地掌握如何使用gRPC进行Go语言开发。