GoLang - grpc

在本篇會透過一個簡單的範例,建立一個簡單的 grpc程序, 在開始前,先確認安裝了protocol buffer compiler

接著,開始說明如何建構基本的 grpc 架構:

(本篇主要參考 grpc-go 官方入門說明)

首先,啟用 go mod 模式

export GO111MODULE=on

接著,安裝 protocol compiler plugin,(這個 plugin 是 grpc-go 的一部分)

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

更新PATH,讓protoc compiler 可以找到plugin

export PATH="$PATH:$(go env GOPATH)/bin"

官方提供一個 grpc-go repository

git clone -b v1.31.0 https://github.com/grpc/grpc-go

前往 example 頁面

cd grpc-go/examples/helloworld

運行 grpc server

go run greeter_server/main.go

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)
	}
}

運行 grpc client

go run greeter_client/main.go

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())
}

server output

Received: world

client output

Greeting: Hello world

新增 grpc 方法:

接著,我們要透過 .proto 來定義新的方法,開啟 helloworld/hellowrold.proto 增加 SayHelloAgain() 方法以及return 的類型:

// The greeting service definition.
service Greeter {
  // Sends a greeting
  rpc SayHello (HelloRequest) returns (HelloReply) {}
  // Sends another greeting
  rpc SayHelloAgain (HelloRequest) returns (HelloReply) {}
}

編譯 proto 自動產生 grpc 程式,

執行以下指令來編譯helloworld.proto

$ ( cd ../../cmd/protoc-gen-go-grpc && go install . )
$ protoc --go_out=. --go-grpc_out=. \
    --go_opt=paths=source_relative \
    --go-grpc_opt=paths=source_relative \
    helloworld/helloworld.proto

編譯完成,會產生 elloworld/helloworld.pb.go ,helloworld/helloworld_grpc.pb.go這兩個檔案

程式內容會包括 populating, serializing以及 retrieving HelloRequestHelloReply 類型,同時產生grpc server 與 grpc client 程式碼。

更新 server

接著回到 greeter_server/main.go 增加 syHelloAgain方法

func (s *server) SayHelloAgain(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
        return &pb.HelloReply{Message: "Hello again " + in.GetName()}, nil
}

更新 client

開啟 greeter_client/main.go ,在main最底部增加以下內容

r, err = c.SayHelloAgain(ctx, &pb.HelloRequest{Name: name})
if err != nil {
        log.Fatalf("could not greet: %v", err)
}
log.Printf("Greeting: %s", r.GetMessage())

重新啟用 grpc server 以及執行 grpc client

會在 client 得到以下輸出

Greeting: Hello world
Greeting: Hello again world