protobuf和gRPC

BigSun丶發表於2024-03-19

目錄
  • 一、grpc介紹
    • 1.1 RPC 介紹
    • 1.2 Protobuf 介紹
  • 二、下載生成proto工具
  • 三、下載go的依賴包
  • 四、快速使用
    • 4.1 編寫proto
    • 4.2 生成go指令碼
    • 4.3 編寫main.go
    • 4.4 修改proto,加入更多引數
    • 4.5 重新生成
    • 4.6 修改程式碼
  • 五、完整的客戶端服務端
    • 5.1 proto檔案
    • 5.2 生成go指令碼
    • 5.3 server.go
    • 5.4 client.go
  • 六、注意
    • 6.1 protoc語法
    • 6.2 mustEmbedUnimplemented***方法問題

一、grpc介紹

  • grpc 是 google 給出的 rpc 呼叫方式,它基於 google 的 protobuf 定義方式,提供了一整套資料定義和 rpc 傳輸的方式

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

https://github.com/grpc/grpc

img

1.1 RPC 介紹

  • 在介紹 grpc 之前有必要首先介紹一下 rpc。RPC 的英文全名是 Remote Procedure Call(遠端過程呼叫),它實現了遠端函式或方法的本地呼叫。由於不在一個記憶體空間,不能直接呼叫,因此需要透過網路來表達呼叫的語義和傳達呼叫的資料。其基本流程如下圖所示。

img

  • 客戶端需要呼叫某個遠端函式,首先需要在 client stub 進行函式語義和資料的網路表達,之後將轉義好的資料透過 sockets 經過網路傳到伺服器端,之後同樣經過 sever stub 的解析呼叫遠端服務的函式。之後透過同樣的鏈路將函式的結果返回給客戶端

1.2 Protobuf 介紹

  • Protobuf 是 Google 給出的一種通用的資料表示方式,透過 proto 檔案定義的資料格式,可以一鍵式的生成 C++,Python,Java 等各種語言實現

  • protobuf經歷了protobuf2和protobuf3,pb3比pb2簡化了很多,目前主流的版本是pb3

img

二、下載生成proto工具

# 下載地址
http://github.com/protocolbuffers/protobuf/releases
# 把可執行檔案加入到環境變數
vim .bash_profile
# proto
PATH=$PATH:/Users/liuqingzheng/soft/protoc-21.0-rc-1-osx-x86_64/bin:
    
####注意:mac下如果下載的是protoc-21.0-rc-1-osx-x86_64,後面使用google內建的proto會找不到,建議下載 https://github.com/protocolbuffers/protobuf/releases/download/v3.20.1/protoc-3.20.1-osx-aarch_64.zip然後配置環境變數

image-20220514031404183

三、下載go的依賴包

# 設定go proxy 代理,下載速度快  
export GOPROXY=https://goproxy.io,direct
# https://goproxy.io/zh/ ,設定成功可以同過go env檢視一下
go env

# 此方式棄用
go get github.com/golang/protobuf/protoc-gen-go
# 使用該方式
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

# 執行成功會在go的bin路徑下生成一個可執行檔案
/Users/liuqingzheng/go/bin/protoc-gen-go
/Users/liuqingzheng/go/bin/protoc-gen-go-grpc

# mac下的話,把該可執行檔案複製到/usr/local/bin/路徑()
cp /Users/liuqingzheng/go/bin/protoc-gen-go  /usr/local/bin/protoc-gen-go
cp /Users/liuqingzheng/go/bin/protoc-gen-go-grpc  /usr/local/bin/protoc-gen-go-grpc

# Goland安裝高亮Protocol 的外掛
# Plugins中搜尋 Protocol Buffers

四、快速使用

4.1 編寫proto

syntax = "proto3";
// 一定要加go的package路徑,否則生成不了,意思是把go檔案生成到proto資料夾下
// ;proto 表示生成的go檔案,包名為proto
option go_package = "../proto;proto";

// 類似於go的結構體,可以定義屬性
message HelloRequest {
  string name = 1; // 1 是編號,不是值
//  int32 age =2
}

4.2 生成go指令碼

// 執行命令
protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/helloworld.proto

// 執行完生成一個helloworld.pb.go
在這個go檔案中,可以找到咱們定義的結構體

image-20220514041007425

4.3 編寫main.go

package main

import (
	"encoding/json"
	"fmt"
	"go_test_learn/proto"
)
import pb "github.com/golang/protobuf/proto" // 匯入這個模組
func main()  {
	// proto 格式編碼解碼
	req:=proto.HelloRequest{Name: "lqz"}
	r,_:=pb.Marshal(&req)
	fmt.Println(string(r))

	// 解碼
	req2:=proto.HelloRequest{Name: "lqz"}
	pb.Unmarshal(r,&req2)
	fmt.Println(req2)

	// json 格式編碼解碼
	type  HelloRequest struct{
		Name string
	}
	h:=HelloRequest{Name: "lqz"}
	r1,_:=json.Marshal(&h)
	fmt.Println(string(r1))

	h1:=HelloRequest{}
	json.Unmarshal(r1,&h1)
	fmt.Println(h1)

}

4.4 修改proto,加入更多引數

syntax = "proto3";
// 一定要加go的package路徑,否則生成不了,意思是把go檔案生成到proto資料夾下
// ;proto 表示生成的go檔案,包名為proto
option go_package = "../proto;proto";

// 類似於go的結構體,可以定義屬性
message HelloRequest {
  string name = 1; // 1 是編號,不是值
  int32 age = 2;
  repeated string girls = 3;

}

4.5 重新生成

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/helloworld.proto

4.6 修改程式碼

package main

import (
	"encoding/json"
	"fmt"
	"go_test_learn/proto"
)
import pb "github.com/golang/protobuf/proto" // 匯入這個模組

func main() {
	// proto 格式編碼解碼
	req := proto.HelloRequest{Name: "lqz", Age: 19, Girls: []string{"劉亦菲", "迪麗熱巴"}}
	r, _ := pb.Marshal(&req)
	fmt.Println(string(r))

	// 解碼
	req2 := proto.HelloRequest{}
	pb.Unmarshal(r, &req2)
	fmt.Println(req2)

	// json 格式編碼解碼
	type HelloRequest struct {
		Name string
		Age int32
		Girls []string
	}
	h := HelloRequest{Name: "lqz",Age: 19, Girls: []string{"劉亦菲", "迪麗熱巴"}}
	r1, _ := json.Marshal(&h)
	fmt.Println(string(r1))

	h1 := HelloRequest{}
	json.Unmarshal(r1, &h1)
	fmt.Println(h1)

}

五、完整的客戶端服務端

5.1 proto檔案

syntax = "proto3";
option go_package = ".;proto";
// 定義一個服務,gRPC自有的,它需要用grpc外掛生成,也就是咱們安裝的那個外掛
service Hello{
  // 服務內有一個函式叫Hello,接收HelloRequest型別引數,返回HelloResponse型別引數
  rpc Hello(HelloRequest) returns(HelloResponse);
}

// 類似於go的結構體,可以定義屬性
message HelloRequest {
  string name = 1; // 1 是編號,不是值
  int32 age = 2;
  repeated string girls = 3;

}
// 定義一個響應的型別
message HelloResponse {
  string reply =1;
}

5.2 生成go指令碼

# protoc 可執行檔案必須加入環境變數

protoc --go_out=. --go_opt=paths=source_relative --go-grpc_out=. --go-grpc_opt=paths=source_relative proto/helloworld.proto

5.3 server.go

package main

import (
	"context"
	"fmt"
	"go_test_learn/proto"
	"google.golang.org/grpc"
	"net"
)

// 定義一個結構體,名字隨意,只要實現Hello方法即可,相當於proto的service
type HelloServer struct {
	proto.UnimplementedHelloServer
}


// 給結構體繫結Hello方法
// 請求引數必須是兩個:context context.Context, request *proto.HelloRequest
// 返回引數必須是兩個:*proto.HelloResponse, error
// 自動生成的go檔案中結構體中的欄位給轉成大寫字母開頭,符合go匯出欄位規範

func (s *HelloServer) Hello(context context.Context, request *proto.HelloRequest) (*proto.HelloResponse, error) {
	fmt.Println(request.Name)
	return &proto.HelloResponse{Reply:"ok"}, nil
}
func main() {
	// 建立一個grpc物件
	g := grpc.NewServer()
	s := HelloServer{}
	// 把s註冊到g物件中
	proto.RegisterHelloServer(g,&s)
	// 監聽埠
	lis,error:=net.Listen("tcp","0.0.0.0:50052")
	if error!=nil{
		panic("啟動服務異常")
	}
	g.Serve(lis)


}

5.4 client.go

package main

import (
	"context"
	"fmt"
	"go_test_learn/proto"
	"google.golang.org/grpc"
	"google.golang.org/grpc/credentials/insecure"
)

func main() {

	// 連線grpc服務端,grpc.WithInsecure()跳過對伺服器的驗證
	//conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithInsecure())
	conn, err := grpc.Dial("127.0.0.1:50052", grpc.WithTransportCredentials(insecure.NewCredentials()))
	if err != nil {
		panic("連線服務異常")
	}
	//defer 關閉
	defer conn.Close()
	//建立一個客戶端,傳入連線物件
	client := proto.NewHelloClient(conn)
	request := proto.HelloRequest{
		Name: "lqz",
	}
	//呼叫SayHello方法,傳入兩個引數
	res, err := client.Hello(context.Background(), &request)
	if err != nil {
		panic("呼叫方法異常")
	}
	//列印出返回的資料
	fmt.Println(res.Reply)
}

六、注意

6.1 protoc語法

-I 或者 --proto_path:用於指定所編譯的原始碼,就是我們所匯入的proto檔案,支援多次指定,按照順序搜尋,如果未指定,則使用當前工作目錄。

--go_out:同樣的也有其他語言的,例如--java_out、--csharp_out,用來指定語言的生成位置,用於生成*.pb.go 檔案

--go_opt:paths=source_relative 指定--go_out生成檔案是基於相對路徑的

--go-grpc_out:用於生成 *_grpc.pb.go 檔案

--go-grpc_opt:

paths=source_relative 指定--go_grpc_out生成檔案是基於相對路徑的

require_unimplemented_servers=false 預設是true,會在server類多生成一個介面

--grpc-gateway_out:是使用到了 protoc-gen-grpc-gateway.exe 外掛,用於生成pb.gw.go檔案

--grpc-gateway_opt:

logtostderr=true 記錄log

paths=source_relative 指定--grpc-gateway_out生成檔案是基於相對路徑的

generate_unbound_methods=true 如果proto檔案沒有寫api介面資訊,也會預設生成

--openapiv2_out:使用到了protoc-gen-openapiv2.exe 外掛,用於生成swagger.json 檔案

6.2 mustEmbedUnimplemented***方法問題

// 新版protoc-gen-go不支援grpc服務生成,需要透過protoc-gen-go-grpc生成grpc服務介面,但是生成的Server端介面中會出現一個mustEmbedUnimplemented***方法,是為了解決前向相容問題,如果不解決,就無法傳遞給RegisterXXXService方法,解決辦法有兩個:
1. 在grpc server實現結構體中匿名嵌入Unimplemented***Server結構體
  type HelloServer struct {
    proto.UnimplementedHelloServer
  }
2. 使用protoc生成server程式碼時命令列加上關閉選項protoc --go-grpc_out=require_unimplemented_servers=false

//protoc --go_out=. --go_opt=paths=source_relative proto/helloworld.proto
//protoc --go-grpc_out=. --go-grpc_opt=require_unimplemented_servers=false --go-grpc_opt=paths=source_relative proto/helloworld.proto

相關文章