方言を話すおしゃべり猫型ロボット『ミーア』をリリースしました(こちらをクリック)

[gRPC] Create a .proto file, compile it, and use it with the Go language.

grpc-create-a-proto-file
この記事は約19分で読めます。

What is gRPC?

gRPC stands for “Google Remote Procedure Call,” an open-source remote procedure call (RPC) framework developed by Google for efficient communication between systems.

Protocol Buffers is a data serialization format developed by Google that expresses data in a smaller size and is faster to send and receive than traditional formats such as JSON and XML. gRPC is a communication framework that utilizes Protocol Buffers.

https://grpc.io/docs/what-is-grpc/introduction/

Click here for an explanation of why protobuf reduces the amount of data.

How to define RPC methods in .proto files

Service Definition:

First, define a service using the service keyword. A service is a collection of a series of RPC methods.

Go
// test.proto
service MyService {
  // RPCメソッドはここに定義される。
}

RPC method definition:

Define methods in the service using the rpc keyword. The method must specify an input message type and an output message type.

The function name is attached only to the request (the method that sends the request) and not to the response type. This is because the response is defined as the return type of the method.

Go
// test.proto
service MyService {
  // 単一のリクエストとレスポンスを持つRPCメソッド
  rpc MyMethod(MyRequest) returns (MyResponse);
}

Message Definition:

Defines the message types used in the input and output of the RPC method.

Go
// test.proto
message MyRequest {
  // リクエストパラメータを定義
}

message MyResponse {
  // レスポンスパラメータを定義
}

streaming

https://grpc.io/docs/languages/go/basics/

Difference between streaming and single request/response models

gRPC also supports streaming RPC.

In traditional RPC communication, the client sends a request to the server, and the server returns a single response. However, streaming allows multiple instances (multiple, consecutive exchanges of data of the same message type) to be sent and received in a single connection.

For example, in server streaming RPC, the client sends a single request and receives multiple responses from the server in chronological order.

Example: A case in which a server sends real-time information on stock prices to a client.

Go
message StockRequest {
  string stock_symbol = 1;
}

message StockResponse {
  string stock_symbol = 1;
  float price = 2;
  string timestamp = 3;
}

service StockService {
  rpc GetStockUpdates(StockRequest) returns (stream StockResponse);
}

The client sends a StockRequest, requesting an update on "AAPL" (Apple Inc. stock symbol). The server receives it and sends a stream of StockResponse messages about Apple’s stock price.

The response from the server is sent in succession as follows.

Go
{ stock_symbol: "AAPL", price: 146.28, timestamp: "2021-07-14T10:00:00Z" }
{ stock_symbol: "AAPL", price: 146.48, timestamp: "2021-07-14T10:01:00Z" }
{ stock_symbol: "AAPL", price: 146.58, timestamp: "2021-07-14T10:02:00Z" }
{ stock_symbol: "AAPL", price: 146.68, timestamp: "2021-07-14T10:03:00Z" }

Each StockResponse message is a different instance of the same message type. Each has different data (in this case, stock price and timestamp), but the message structure is the same. In this way, the server can continue to send information to the client in real-time. The client receives these messages and processes them accordingly.

Three streaming types (client, server, and bidirectional)

There are three types of streaming: client streaming RPC, server streaming RPC, and bidirectional streaming RPC.

When streaming is used, precede the message definition with the stream keyword.

Client Streaming RPC

  • The client sends a stream to the server and the server sends back a single response. Useful for sending large amounts of data to the server.
  • Example: File upload process: The client splits the file into small chunks and sends the chunks to the server in sequence. The server receives the chunks and reconstructs the original file. When the upload is complete, the server sends a single response to the client regarding the success or failure of the upload.
Go
rpc ClientStreaming(stream MyRequest) returns (MyResponse);

Server streaming RPC:

  • The client sends a request and the server sends back a stream of responses. Useful when the server sends continuous data to the client.
  • Example: Streaming video playback, real-time data feeds (e.g., stock price information)
Go
rpc ServerStreaming(MyRequest) returns (stream MyResponse);

Bidirectional Streaming RPC

  • The client and server send and receive streams to and from each other. Useful when data needs to be exchanged in both directions.
  • Example: Applications requiring real-time interaction: communication in chat applications/real-time games
Go
rpc BidirectionalStreaming(stream MyRequest) returns (stream MyResponse);

These definitions are written in a .proto file and the protobuf compiler ( protoc) is used to generate source code for the selected language.

Generate Go code from.protofile

After you have finished writing messages and RPC methods in the .proto file, use the protoc command to generate Go source code from the .proto file. You need to install protoc-gen-go andprotoc-gen-go-grpc plug-ins beforehand.

Go
$ protoc --go_out=. --go-grpc_out=. path/to/yourfile.proto

This command generates a Go code from the specified .proto file and outputs it to the current directory. --go_out=. and --go-grpc_out=. options specify that the file should be generated in the current directory. If you want to generate files in a different directory, change it.

For example, if the file is to be generated in the pb directory, the following.

Go
$ protoc --go_out=pb --go-grpc_out=pb path/to/yourfile.proto

For example, when the following example.proto file is compiled into code for the Go language, two files are generated.

Go
// ファイル名: example.proto
syntax = "proto3";

package pb; //名前空間の衝突を避けるためにpackageを定義

message Request {
  string query = 1;
}

message Response {
  string result = 1;
}

service ExampleService {
  rpc GetResponse(Request) returns (Response);
}

example.pb.go

This file contains Go structures corresponding to Request andResponse messages and functions to manipulate them (getter functions, etc.). These are generated directly from the protocol buffer definitions.

Protobuf is a tool that can generate code for a specific programming language from a language-independent schema definition. The generated code is in a format appropriate for the language, including message structure, serialization/deserialization logic, accessors, etc. In the case of the Go language, the messages in the .proto file are compiled as structures (struct). These structs contain field tags to which Protobuf field numbers are mapped.

Incidentally, in Dart, the message definitions in the .proto file is compiled as a class, and each field defined in the message is compiled as a property of the class. The role is the same as that of Go language structures.

Go
// example.pb.go(概要)
package pb

type Request struct {
    Query string `protobuf:"bytes,1,opt,name=query,proto3" json:"query,omitempty"`
}

func (x *Request) Reset() {
    *x = Request{}
}

func (x *Request) String() string {
    return protoimpl.X.MessageStringOf(x)
}

type Response struct {
    Result string `protobuf:"bytes,1,opt,name=result,proto3" json:"result,omitempty"`
}

func (x *Response) Reset() {
    *x = Response{}
}

func (x *Response) String() string {
    return protoimpl.X.MessageStringOf(x)
}

func (m *Request) GetQuery() string {
    if m != nil {
        return m.Query
    }
    return ""
}

example_grpc.pb.go:.

This file contains the gRPC-related code for the ExampleService service. It contains both client and server interface definitions. On the server side, it provides the basis for implementing the service interface, and on the client side, it provides functions for calling the server’s methods.

Go
// example_grpc.pb.go(概要)
package pb

type ExampleServiceClient interface {
    GetResponse(ctx context.Context, in *Request, opts ...grpc.CallOption) (*Response, error)
}

type ExampleServiceServer interface {
    GetResponse(context.Context, *Request) (*Response, error)
}

func RegisterExampleServiceServer(s *grpc.Server, srv ExampleServiceServer) {
    s.RegisterService(&_ExampleService_serviceDesc, srv)
}

// その他のクライアントやサーバーの実装の詳細

When implementing ExampleService on the server side, implement the GetResponse method according to the ExampleServiceServer interface. On the client side, call the server methods using the ExampleServiceClient interface.

Example of RPC method usage in Go language (Go gRPC API)

Example of GetResponse RPC method usage with gRPC server and client

Server-side implementation

On the server side, the ExampleServiceServer the interface must be implemented.

pb corresponds to the package name defined in the .proto file, which will be the package name in the generated Go code. types such asUnimplementedExampleServiceServer, Request, and Response are all defined in the pb package.

Go
package main

import (
    "context"
    "log"
    "net"

    "google.golang.org/grpc"
    pb "path/to/your/pb" // pbパッケージへのパスを適切に設定
)

type server struct {
    pb.UnimplementedExampleServiceServer
}

func (s *server) GetResponse(ctx context.Context, in *pb.Request) (*pb.Response, error) {
    log.Printf("Received: %v", in.GetQuery())
    return &pb.Response{Result: "Hello " + in.GetQuery()}, nil
}

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

Client-side implementation

On the client side, connect to the server and call the GetResponse method.

The client code connects to the server and calls GetResponse RPC to get the response to the query “world”. The server adds “Hello” to the query and returns it, so the client receives the result “Hello world” and outputs it to the log.

Go
package main

import (
    "context"
    "log"
    "time"

    "google.golang.org/grpc"
    pb "path/to/your/pb" // pbパッケージへのパスを適切に設定
)

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

    ctx, cancel := context.WithTimeout(context.Background(), time.Second)
    defer cancel()
    r, err := c.GetResponse(ctx, &pb.Request{Query: "world"})
    if err != nil {
        log.Fatalf("could not greet: %v", err)
    }
    log.Printf("Greeting: %s", r.GetResult())
}

コメント

タイトルとURLをコピーしました