我尝试使用Golang创建了gRPC。(Wǒ chángshì yòng Golang chuàngjiànle gRPC.)

我正在使用Mac(M1)。

我想要创建的服务

当我发送姓名(Cli)时,会收到一些回复(SV)。
具体来说,我将创建以下四个方法。

    1. 发送名字后,会返回“你好,名字”。

 

    1. 发送名字后,会返回“你好,名字!”,发送名字多次会返回“你好,名字1、名字2、…”。

 

    1. 发送多个名字后,会返回相应数量的“你好,名字”。

 

    发送任意数量的名字后,会返回相同数量的“你好,名字”。

gRPC是什么(非常简单地解释)

这太好了

目录结构

current dir
    └──hello
          ├── client
          ├── proto
          └── server

首先是Protocol Buffer的描述

// バージョン
syntax = "proto3";

// Goのパッケージ名として使われる。
// 別Protoファイルで同じmessageが使用されていた場合に衝突を回避できる。
package hello;

// Goのインポートパス
option go_package = "github.com/hirohokke/grpc-go/hello/proto";

// リクエストを宣言(Goのstructのようなもの)
message HelloRequest {
    // 型 フィールド名 = タグ;
    string first_name = 1;
}

// レスポンスを宣言(Goのstructのようなもの)
message HelloResponse {
    string result = 1;
}

// サービスを宣言
// ここに宣言されたRPCをクライアントから呼び出す
service HelloService {
    // リクエストを渡して、レスポンスを返す
    rpc Hello (HelloRequest) returns (HelloResponse);
    // レスポンスにstreamを付けることでSVは複数レスポンスを返せる
    rpc HelloAmp (HelloRequest) returns (stream HelloResponse);
    // リクエストにstreamを付けることでクライアントは複数リクエストを渡せる
    rpc HelloManyTimes (stream HelloRequest) returns (HelloResponse);
    // リクエスト、レスポンスにstreamを付けることで双方向でストリーミングできる
    rpc HelloEveryone (stream HelloRequest) returns (stream HelloResponse);
}

详细的事情,官方的或者这是日语的。

关于protobuf的简单解释:

    • データを構造化してシリアライズするための、Googleによって作られた機構。

 

    • バイナリでシリアライズされるので、サイズが小さくPayloadが小さくて通信が早くなる。(よく使われるJSONやXMLに比べて←プレーンテキスト)

 

    • 型宣言するので、型の安全性が保証される。

 

    • いろんな言語に対応

 

    etc.

protobuf的编译

首先,安装protobuf。

$ brew install protobuf

安装适用于Go的protobuf运行时。

$ go get -u google.golang.org/grpc
$ go get -u google.golang.org/grpc/cmd/protoc-gen-go-grpc

编译为Go可执行文件

$ protoc -Ihello/proto \
         --go_opt=module=github.com/hirohokke/grpc-go \
         --go_out=. \
         --go-grpc_opt=module=github.com/hirohokke/grpc-go\
         --go-grpc_out=. hello/proto/*.proto

这样做的话,会在hello/proto文件夹下生成Go代码。
I就像解决依赖关系一样。虽然这次没有。

创建 Go 文件

由于准备工作完成,创建Go文件。
※虽然可能没有进行错误处理等操作,请不要在意。它只是随意的。

服务器

package main

import (
	pb "github.com/hirohokke/grpc-go/hello/proto"
	"google.golang.org/grpc"
	"log"
	"net"
)

// gRPCサーバーで受け付けるアドレス
var addr string = "0.0.0.0:50051"

// gRPCサーバーの構造体を宣言
type Server struct {
	pb.HelloServiceServer
}

func main() {
	// 指定したアドレスでTCPプロトコルで通信受付インスタンス生成
	lis, err := net.Listen("tcp", addr)
	if err != nil {
		log.Fatalf("Failed to listen on: %v\n", err)
	}

	log.Printf("Listen on %s\n", addr)

	// grpcサーバーを生成し、Helloサービスを設定
	s := grpc.NewServer()
	pb.RegisterHelloServiceServer(s, &Server{})

	// サーバー起動
	if err = s.Serve(lis); err != nil {
		log.Fatalf("Failed to serve: %v\n", err)
	}
}

接下来,我们需要实现在 protobuf 中描述的 RPC 服务。

package main

import (
	"context"
	"fmt"
	pb "github.com/hirohokke/grpc-go/hello/proto" // protobufのインポート
	"io"
	"log"
	"strings"
)

// 1. 名前を送ると、”Hello FirstName”を返す
func (s *Server) Hello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloResponse, error) {
	log.Printf("Helloが呼び出されました: %v\n", in)
	// ここでは、文字列を生成し、レスポンスを返すだけ。
	return &pb.HelloResponse{
		Result: fmt.Sprintf("Hello %s", in.FirstName),
	}, nil
}

// 2. !が増幅して返ってくる
func (s *Server) HelloAmp(in *pb.HelloRequest, stream pb.HelloService_HelloAmpServer) error {
	log.Printf("HelloAmpが呼び出されました: %v\n", in)

	for i := 0; i < 5; i++ {
		// レスポンス作成
		res := fmt.Sprintf("Hello %s%s", in.FirstName, strings.Repeat("!", i))

		// クライアントにレスポンスを送信
		err := stream.Send(&pb.HelloResponse{Result: res})
		if err != nil {
			log.Fatalf("レスポンス送信中にエラーが発生: %v\n", err)
		}

	}

	return nil
}

// 3. 複数回送られてくるリクエストを結合して返す
func (s *Server) HelloManyTimes(stream pb.HelloService_HelloManyTimesServer) error {
	log.Println("HelloManyTimesが呼び出されました。")

	res := "Hello"

	// 無限ループでリクエストを受け取り続ける
	for {
		// リクエストを取得
		req, err := stream.Recv()

		// リクエスト終端の場合、レスポンスを送信
		if err == io.EOF {
			return stream.SendAndClose(&pb.HelloResponse{
				Result: res,
			})
		}

		// 終端以外のエラーの場合
		if err != nil {
			log.Fatalf("リクエスト読み取りでエラー発生: %v\n", err)
		}

		res = res + " " + req.FirstName
	}
}

// 4. リクエストごとに返信する
func (s *Server) HelloEveryone(stream pb.HelloService_HelloEveryoneServer) error {
	log.Println("HelloEveryoneが呼び出されました")

	for {
		// streamからリクエストを受け取り
		req, err := stream.Recv()
		if err == io.EOF {
			return nil
		}
		if err != nil {
			log.Fatalf("リクエスト読み取り中にエラー発生: %v\n", err)
		}

		res := fmt.Sprintf("Hello %s", req.FirstName)
		// streamにレスポンスを送信
		err = stream.Send(&pb.HelloResponse{Result: res})
		if err != nil {
			log.Fatalf("レスポンス送信中にエラー発生: %v\n", err)
		}
	}
}

然后是客户

客户

package main

import (
	pb "github.com/hirohokke/grpc-go/hello/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
	"log"
)

// 作成したgRPCサーバーのアドレス
var addr string = "localhost:50051"

func main() {
	// 作ったサーバーとのコネクタを作成
	// ssl認証なし
	conn, err := grpc.Dial(addr, grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		log.Fatalf("接続失敗: %v\n", err)
	}
	defer conn.Close()

	// gRPCクライアントの生成
	c := pb.NewHelloServiceClient(conn)

	doHello(c)
	doHelloAmp(c)
	doHelloManyTimes(c)
	doHelloEveryone(c)
}

呼叫部分 (hū bù fen)

package main

import (
	"context"
	pb "github.com/hirohokke/grpc-go/hello/proto"
	"io"
	"log"
	"time"
)

// 1. Hello呼び出し
func doHello(c pb.HelloServiceClient) {
	log.Println("---doHello---")

	// リクエストを作成
	req := &pb.HelloRequest{FirstName: "Hokke"}

	// Helloサービス呼び出し
	res, err := c.Hello(context.Background(), req)
	if err != nil {
		log.Fatalf("Helloの呼び出しでエラー: %v\n", err)
	}
	log.Printf("%s\n", res.Result)
}

// 2. HelloAmp呼び出し
func doHelloAmp(c pb.HelloServiceClient) {
	log.Println("---doHelloAmp---")

	// リクエスト作成
	req := &pb.HelloRequest{FirstName: "Hokke"}

	// gRPCとのstream生成
	stream, err := c.HelloAmp(context.Background(), req)
	if err != nil {
		log.Fatalf("HelloAmp呼び出しでエラー: %v\n", err)
	}

	// EOFが送られるまでレスポンスを取得し続けるため、無限ループ
	for {
		// レスポンス取得
		res, err := stream.Recv()

		// レスポンスが終わった場合
		if err == io.EOF {
			break
		}

		if err != nil {
			log.Fatalf("レスポンス取得でエラー発生: %v\n", err)
		}

		log.Printf("Response is %s\n", res.Result)
	}
}

// 3. HelloManyTimes呼び出し
func doHelloManyTimes(c pb.HelloServiceClient) {
	log.Println("---doHelloManyTimes---")

	// stream生成
	stream, err := c.HelloManyTimes(context.Background())
	if err != nil {
		log.Fatalf("HelloManyTimes呼び出しでエラー")
	}

	// リクエスト生成
	reqs := []*pb.HelloRequest{
		{FirstName: "Hokke"},
		{FirstName: "Hiro"},
		{FirstName: "Hoge"},
	}

	// リクエスト数分送信
	for _, req := range reqs {
		log.Printf("Sending Request: %v\n", req)
		stream.Send(req)
	}

	res, err := stream.CloseAndRecv()
	if err != nil {
		log.Fatalf("レスポンス取得でエラー: %v\n", err)
	}

	log.Println(res.Result)
}

// 4. HelloEveryone呼び出し
func doHelloEveryone(c pb.HelloServiceClient) {
	log.Println("---doHelloEveryone---")

	stream, err := c.HelloEveryone(context.Background())
	if err != nil {
		log.Fatalf("呼び出しでエラー")
	}

	reqs := []*pb.HelloRequest{
		{FirstName: "Hokke"},
		{FirstName: "Hiro"},
		{FirstName: "Hoge"},
	}

	// ゴルーチンを使うので、待機チャネルを使用
	waitc := make(chan struct{})

	go func() {
		for _, req := range reqs {
			log.Printf("Sending request: %v\n", req)
			stream.Send(req)
			// わかりやすいように、1s待つ
			time.Sleep(1 * time.Second)
		}
		stream.CloseSend()
	}()

	go func() {
		for {
			res, err := stream.Recv()
			if err == io.EOF {
				break
			}
			if err != nil {
				log.Fatalf("レスポンス取得中にエラー: %v\n", err)
			}
			log.Printf("Response is %s\n", res.Result)
		}
		close(waitc)
	}()

	<-waitc
}

进行

创建服务器和客户端模块

$ go build -o bin/server ./hello/server
$ go build -o bin/client ./hello/client

只需要执行就可以了

$ ./bin/server
2023/04/26 22:04:55 Listen on 0.0.0.0:50051
$ ./bin/client
2023/04/26 22:05:03 ---doHello---
2023/04/26 22:05:03 Hello Hokke
2023/04/26 22:05:03 ---doHelloAmp---
2023/04/26 22:05:03 Response is Hello Hokke
2023/04/26 22:05:03 Response is Hello Hokke!
2023/04/26 22:05:03 Response is Hello Hokke!!
2023/04/26 22:05:03 Response is Hello Hokke!!!
2023/04/26 22:05:03 Response is Hello Hokke!!!!
2023/04/26 22:05:03 ---doHelloManyTimes---
2023/04/26 22:05:03 Sending Request: first_name:"Hokke"
2023/04/26 22:05:03 Sending Request: first_name:"Hiro"
2023/04/26 22:05:03 Sending Request: first_name:"Hoge"
2023/04/26 22:05:03 Hello Hokke Hiro Hoge
2023/04/26 22:05:03 ---doHelloEveryone---
2023/04/26 22:05:03 Sending request: first_name:"Hokke"
2023/04/26 22:05:03 Response is Hello Hokke
2023/04/26 22:05:04 Sending request: first_name:"Hiro"
2023/04/26 22:05:04 Response is Hello Hiro
2023/04/26 22:05:05 Sending request: first_name:"Hoge"
2023/04/26 22:05:05 Response is Hello Hoge
$ ./bin/server 
2023/04/26 22:04:55 Listen on 0.0.0.0:50051
2023/04/26 22:05:03 Helloが呼び出されました: first_name:"Hokke"
2023/04/26 22:05:03 HelloAmpが呼び出されました: first_name:"Hokke"
2023/04/26 22:05:03 HelloManyTimesが呼び出されました。
2023/04/26 22:05:03 HelloEveryoneが呼び出されました

在客户端一次性调用服务,但分别调用更容易理解处理。

赠品

关于错误的问题

服务器可以返回错误给客户端。

// 処理
if err != nil {
    return status.Errorf(
        codes.Internal, // 例)内部エラー
        fmt.Sprinf("内部エラー発生"),
    )
}
// 例
err := callGRPC()
if err != nil {
    e, ok := status.FromError(err)
    if ok {
        if e.Code() == codes.Internal {
            // エラーハンドリング
        } else {
            log.Fatalf("Unexpected gRPC error: %v\n", err)
        }
    } else {
        log.Fatalf("A non gRPC error: %v\n", err)
    }
}
广告
将在 10 秒后关闭
bannerAds