golang開發一個簡單的grpc

slowquery發表於2022-10-13

0.1、索引

waterflow.link/articles/1665674508...

1、什麼是grpc

在 gRPC 中,客戶端應用程式可以直接呼叫不同機器上的伺服器應用程式上的方法,就像它是本地物件一樣,使您更容易建立分散式應用程式和服務。 與許多 RPC 系統一樣,gRPC 基於定義服務的思想,指定可以遠端呼叫的方法及其引數和返回型別。 在服務端,服務端實現這個介面並執行一個 gRPC 伺服器來處理客戶端呼叫。 在客戶端,客戶端有一個stub(在某些語言中僅稱為客戶端),它提供與伺服器相同的方法。
https://i.iter01.com/images/84384dcf34984b79f3843efad51ac97f9b19d67eab14dc2cb790f2172bf20693.png

所以grpc是跨語言的。

2、什麼是Protocol Buffers

Protocol Buffers提供了一種語言中立、平臺中立、可擴充套件的機制,用於以向前相容和向後相容的方式序列化結構化資料。 它類似於 JSON,只是它更小更快,並且生成本地語言繫結。

可以透過 .proto定義資料結構,然後就可以使用Protocol Buffers編譯器 protoc 從. proto 定義中生成我們喜歡的語言的資料訪問類。 它們為每個欄位提供簡單的訪問器,如 name() 和 set_name(),以及將整個結構序列化/解析到原始位元組/從原始位元組中提取的方法。

3、grpc服務端

1、首先我們需要下載go對應的protoc外掛

go install google.golang.org/protobuf/cmd/protoc-gen-go@v1.28
go install google.golang.org/grpc/cmd/protoc-gen-go-grpc@v1.2

然後把GOPATH放到PATH

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

接著列印下看看有沒有進去

echo $PATH
/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:$GOPATH/bin:/usr/local/go/bin

接著開新視窗,執行下面命令看下protoc是否安裝成功

protoc --version
libprotoc 3.19.1

2、接著我們建立一個hello.proto

內容如下(不懂結構的可自行百度)

syntax = "proto3";

package helloservice;

option go_package = ".;helloservice"; // 指定包名

message String {
  string value = 1;
}

service HelloService {
  rpc Hello(String) returns (String); // 一元方法
  rpc Channel (stream String) returns (stream String); // 流式方法
}

目錄結構如下

.
├── go.mod
├── go.sum
├── helloclient
│   └── main.go
├── helloservice
│   ├── hello.proto

3、接著命令列生成對應語言的類程式碼

cd helloservice
 protoc --go_out=./ --go-grpc_out=./ hello.proto

我們可以看下現在的目錄結構(其他檔案目錄可忽略,後面會建立,現在只需要關注hello_grpc.pb.go)

.
├── go.mod
├── go.sum
├── helloclient
│   └── main.go
├── helloservice
│   ├── hello.pb.go
│   ├── hello.proto
│   ├── hello_grpc.pb.go
│   ├── hello_service.go
│   └── main
│       └── main.go

4、實現自己的hello service

在上面生成的hello_grpc.pb.go中我們可以看到這樣的介面

// HelloServiceServer is the server API for HelloService service.
// All implementations must embed UnimplementedHelloServiceServer
// for forward compatibility
type HelloServiceServer interface {
    Hello(context.Context, *String) (*String, error)
    Channel(HelloService_ChannelServer) error
    mustEmbedUnimplementedHelloServiceServer()
}

翻譯一下就是

// HelloServiceServer 是 HelloService 服務的服務端 API。
// 所有實現都必須嵌入 UnimplementedHelloServiceServer
// 為了向前相容

所以我們在helloservice中建立一個hello_service.go檔案,用來實現上面的介面

package helloservice

import (
    "context"
    "io"
    "time"
)

type HelloService struct {
}

func (h HelloService) mustEmbedUnimplementedHelloServiceServer() {
    panic("implement me")
}

func (h HelloService) Hello(ctx context.Context, args *String) (*String, error) {
    time.Sleep(time.Second)
    reply := &String{Value: "hello:" + args.GetValue()}
    return reply, nil
}

func (h HelloService) Channel(stream HelloService_ChannelServer) error {
    for {
        recv, err := stream.Recv()
        if err != nil {
            if err == io.EOF {
                return nil
            }
            return err
        }

        reply := &String{Value: "hello:" + recv.Value}
        err = stream.Send(reply)
        if err != nil {
            return err
        }
    }
}

上面的方法和簡單,就是列印我們自定義的字串。

然後我們在main中編寫下服務啟動的程式碼

package main

import (
    "google.golang.org/grpc"
    "grpcdemo/helloservice"
    "log"
    "net"
)

func main() {
  // NewServer 建立一個 gRPC 伺服器,它沒有註冊服務,也沒有開始接受請求。
    grpcServer := grpc.NewServer()
  // 註冊服務
    helloservice.RegisterHelloServiceServer(grpcServer, new(helloservice.HelloService))

  // 開啟一個tcp監聽
    listen, err := net.Listen("tcp", ":1234")
    if err != nil {
        log.Fatal(err)
    }
    log.Println("server started...")
  // 在監聽器 listen 上接受傳入的連線,建立一個新的ServerTransport 和 service goroutine。 服務 goroutine讀取 gRPC 請求,然後呼叫註冊的處理程式來回復它們。
    log.Fatal(grpcServer.Serve(listen))
}

然後我們啟動下看下效果

go run helloservice/main/main.go
2022/10/13 23:07:46 server started...

4、grpc客戶端

接著我們編寫客戶端的程式碼helloclient/main.go

package main

import (
    "context"
    "fmt"
    "google.golang.org/grpc"
    "grpcdemo/helloservice"
    "io"
    "log"
    "time"
)

func main() {
    // 連線grpc服務端
    conn, err := grpc.Dial("localhost:1234", grpc.WithInsecure())
    if err != nil {
        log.Fatal(err)
    }
    defer conn.Close()

  // 一元rpc
    unaryRpc(conn)
  // 流式rpc
    streamRpc(conn)

}


func unaryRpc(conn *grpc.ClientConn) {
  // 建立grpc客戶端
    client := helloservice.NewHelloServiceClient(conn)
  // 傳送請求
    reply, err := client.Hello(context.Background(), &helloservice.String{Value: "hello"})
    if err != nil {
        log.Fatal(err)
    }
    log.Println("unaryRpc recv: ", reply.Value)
}

func streamRpc(conn *grpc.ClientConn) {
  // 建立grpc客戶端
    client := helloservice.NewHelloServiceClient(conn)
  // 生成ClientStream
    stream, err := client.Channel(context.Background())
    if err != nil {
        log.Fatal(err)
    }

    go func() {
        for {
      // 傳送訊息
            if err := stream.Send(&helloservice.String{Value: "hi"}); err != nil {
                log.Fatal(err)
            }
            time.Sleep(time.Second)
        }
    }()

    for {
    // 接收訊息
        recv, err := stream.Recv()
        if err != nil {
            if err == io.EOF {
                break
            }
            log.Fatal(err)
        }

        fmt.Println("streamRpc recv: ", recv.Value)

    }
}
本作品採用《CC 協議》,轉載必須註明作者和本文連結

相關文章