grpc 筆記

miss201發表於2018-07-23

gRPC 是一個高效能、開源和通用的 RPC 框架,面向移動和 HTTP/2 設計。目前提供 C、Java 和 Go 語言版本,分別是:grpc, grpc-java, grpc-go. 其中 C 版本支援 C, C++, Node.js, Python, Ruby, Objective-C, PHP 和 C# 支援.

gRPC 基於 HTTP/2 標準設計,帶來諸如雙向流、流控、頭部壓縮、單 TCP 連線上的多複用請求等特。這些特性使得其在移動裝置上表現更好,更省電和節省空間佔用。

1.要編譯proto檔案生成go程式碼需要兩個工具:

protoc :用於編譯(其他語言只需要protoc足以)
protoc-gen-go : 用於生成go語言的檔案(go語言專用外掛)
由於有牆,所以在這依賴或者工具都去 github 上去找
protoc-gen-go下載:go get github.com/golang/protobuf/protoc-gen-go
protoc :https://github.com/google/protobuf/releases 下載對應的 OS 要的安裝包。
配置環境變數:解壓後找到對應的 bin 目錄,file
配置完,在命令列中輸入 protoc,會有以下輸出,即表示安裝成功:
file

2.安裝 grpc

go get google.golang.org/grpc

3.目錄結構

file

protos
--protos.pb.go 編譯proto之後生成go檔案
--protos.proto 要編寫的proto檔案
service
-- service.go 服務端的業務
service.go 服務端的啟動入口
client.go 客戶端的入口

4. 編寫protos.proto 檔案

syntax = "proto3";

package protos;

message User{
  int32 id =1 ;
  string name =2 ;
}

message UserReq{
    int32 id =1;
}

service IUserService{
    // 單一請求應答,一對一
    rpc Get (UserReq) returns (User);
    // 服務端流式應答,一對多,可用於下載
    rpc GetList (UserReq) returns (stream User);
    // 客戶端流式請求,多對一,可用於上傳
    rpc WaitGet(stream UserReq) returns (User);
    // 雙向流式請求應答,支援HTTP/2.0
    rpc LoopGet(stream UserReq) returns (stream User);
}

cd 到 protos.proto 所在的目錄:
在命令列中輸入:protoc --go_out=plugins=grpc:. ./protos.proto,將會生成 protos.pb.go 檔案。

*rpc Get (google.protobuf.Empty) returns (User) 方法裡面的出參跟入參都不能不填,如果要為空 ,可以匯入 import "google/protobuf/empty.proto" 裡面有個 message Empty {} **

5.實現服務端 service/service.go

package service

import (
    "goweb/protos"
    "fmt"
    "strconv"
    "io"
    "golang.org/x/net/context"
)

//對外提供的工廠函式
func NewUserService() *UserService {
    return &UserService{}
}

//**************************************************************
// 介面實現,介面定義是在proto生成的.pb.go檔案中
//**************************************************************

// 介面實現物件,屬性成員根據而業務自定義
type UserService struct {
}

// Get介面方法實現
func (this *UserService) Get(ctx context.Context, req *protos.UserReq) (*protos.User, error) {
    return &protos.User{Id: 1, Name: "shuai"}, nil
}

// GetList介面方法實現
func (this *UserService) GetList(req *protos.UserReq, stream protos.IUserService_GetListServer) error {
    fmt.Println(*req)
    // 流式返回多條資料
    for i := 0; i < 5; i++ {
        stream.Send(&protos.User{Id: int32(i), Name: "我是" + strconv.Itoa(i)})
    }
    return nil
}

// WaitGet介面方法實現
func (this *UserService) WaitGet(reqStream protos.IUserService_WaitGetServer) error {
    for { // 接收流式請求並返回單一物件
        userReq, err := reqStream.Recv()
        if err != io.EOF {
            fmt.Println("流請求~", *userReq)
        } else {
            return reqStream.SendAndClose(&protos.User{Id: 100, Name: "shuai"})
        }
    }
}

//雙向流:請求流和響應流非同步
func (this *UserService) LoopGet(reqStream protos.IUserService_LoopGetServer) error {
    for {
        userReq, err := reqStream.Recv()
        if err == io.EOF { //請求結束
            return nil
        }
        if err != nil {
            return err
        }
        if err = reqStream.Send(&protos.User{Id: userReq.Id, Name: "shuai"}); err != nil {
            return err
        }
    }
}

6.啟動服務端 goweb/service.go

package main

import (
    "flag"
    "fmt"
    "net"
    "goweb/service" // 實現了服務介面的包service
    "goweb/protos" // 此為自定義的protos包,存放的是.proto檔案和對應的.pb.go檔案

    "google.golang.org/grpc"
)

var (
    // 命令列引數-host,預設服務監聽埠在9000
    addr = flag.String("host", "127.0.0.1:9000", "")
)

func main() {
    // 開啟服務監聽
    lis, err := net.Listen("tcp", *addr)
    if err != nil {
        fmt.Println(err)
        fmt.Println("listen error!")
        return
    }
    // 建立一個grpc服務
    grpcServer := grpc.NewServer()
    // 重點:向grpc服務中註冊一個api服務,這裡是UserService,處理相關請求
    protos.RegisterIUserServiceServer(grpcServer, service.NewUserService())
    // 可以新增多個api
    // TODO...

    // 啟動grpc服務
    grpcServer.Serve(lis)
}

cd 到 goweb 目錄,輸入go run service.go 啟動服務。

7.客戶端的實現 goweb/client.go

package main

import (
    "flag"
    "fmt"
    "io"
    "log"
    "goweb/protos" // 此為自定義的protos包,存放的是.proto檔案和對應的.pb.go檔案
    "golang.org/x/net/context"
    "google.golang.org/grpc"
)

var (
    // 命令列引數-host,預設地址本機9000埠
    addr1 = flag.String("host", "127.0.0.1:9000", "")
    // UserService服務存根,可直接呼叫服務方法
    userCli protos.IUserServiceClient
)

func main() {
    // 建立grpc的服務連線
    conn, err := grpc.Dial(*addr1,grpc.WithInsecure())
    if err != nil {
        log.Fatal("failed to connect : ", err)
    }
    // 存根
    userCli = protos.NewIUserServiceClient(conn)

    // 測試前三種資料傳遞方式,第四種省略(二三結合)
    TestGet()
    TestGetList()
    TestWaitGet()
}

func TestGet() {
    // 測試呼叫方法Get,返回user物件
    user, err := userCli.Get(context.Background(), &protos.UserReq{Id: 1})
    if err != nil {
        fmt.Printf("Get connect failed :%v", err)
        return
    }
    log.Println("Get響應資料:", *user)
}

func TestGetList() {
    // 測試呼叫方法GetList,返回一個Stream流,迴圈獲取多個user物件
    recvStream, err := userCli.GetList(context.Background(), &protos.UserReq{Id: 1})
    if err != nil {
        fmt.Printf("GetList connect failed :%v", err)
        return
    }
    for {
        user, err := recvStream.Recv()
        if err == io.EOF {
            break
        }
        log.Println("GetList獲取的一條響應資料:", *user)
    }
}

func TestWaitGet() {
    // 測試呼叫方法WaitGet,傳入多條請求資料,返回一個user物件
    sendStream, err := userCli.WaitGet(context.Background())
    if err != nil {
        fmt.Printf("WaitGet connect failed :%v", err)
        return
    }
    for i := 0; i < 5; i++ { // 一次傳入5條請求資料
        if err = sendStream.Send(&protos.UserReq{Id: int32(i)}); err != nil {
            fmt.Printf("WaitGet send failed :%v", err)
            return
        }
    }
    // 服務端接受全部請求資料後,返回一個user物件
    user, err := sendStream.CloseAndRecv()
    if err != nil {
        fmt.Printf("WaitGet recv failed :%v", err)
        return
    }
    log.Println("WaitGet響應資料:", *user)
}

cd 到 goweb 目錄,輸入go run client.go:
file
看到輸入結果,即表示 grpc 呼叫成功。
~下次再來分析裡面的細節~
文章參考的:https://blog.csdn.net/ys5773477/article/details/77834697

不卑不亢,不慌不忙,這才是生活的模樣。

相關文章