日本免费高清视频-国产福利视频导航-黄色在线播放国产-天天操天天操天天操天天操|www.shdianci.com

學無先后,達者為師

網站首頁 編程語言 正文

Golang安裝和使用protocol-buffer流程介紹_Golang

作者:whynogome ? 更新時間: 2022-11-06 編程語言

前言

protocol buffer是Google發布的一種獨立的數據交換格式,類似于json,用于數據的序列化和解析。不同點是不能直接在各編程語言中使用,需要先在一個proto文件中定義需要傳輸的數據格式,然后使用proto工具把proto文件編譯成想要的語言,如java、go、php等。然后在代碼中,使用語言對應的protocol buffer包調用proto工具生成的文件,完成數據的序列化

安裝protoc編譯工具

首先安裝protoc編譯工具,在http://github.com/google/protobuf/releases,根據自己的系統選擇包,下載后解壓。我是win10 64,所以選擇protoc-21.4-win64.zip,解壓后放在了E盤。

添加環境變量,讓系統命令可以識別protoc命令。

配置環境變量

PB_PATH=E:\protoc-3.21.4-win64

PATH=%PB_PATH%\bin

添加后,在命令行執行 protoc,返回信息,表示安裝成功

編寫proto文件

創建一個user.proto文件,該文件下可以定義一些user相關的需要加密的字段

//引入其他proto文件,就可以使用該文件中定義的message類型,定義本文件中的message下的字段
import "myproject/other_protos.proto";
// 指定的當前proto語法的版本
syntax = "proto3";
// 指定生成出來的文件的package,用來避免同一個文件調同名的消息時沖突
// 包說明符對生成代碼的影響取決于您選擇的語言,php會生成命名空間,go會生成package
package service;
//根據編程語言不同,參數名不同
//下面代碼指定了生成go文件的生成目錄,文件的包名。不設置包名時,包名默認為go文件所在目錄名。會覆蓋package設置的go的包名
//路徑以執行命令的目錄為當前目錄,尋找相對路徑
option go_package="./;service";
//定義消息類型,通過關鍵字message字段指定的,消息就是需要傳輸的數據格式的定義
message User {
// required必傳;optional 可選;repeated 可重復初入。不寫前綴默認為required
// 字段名后的數字不是默認值,是標識。標識號是[0,2^29-1]范圍內的一個整數。[1-15]內的標識號在編碼時只占用一個字節,包含標識符和字段類型,[16-2047]之間的標識符占用2個字節。建議為頻繁出現的字段使用[1-15]間的標識符。
//字段類型除了除了常規類型,也可使用import引入文件中定義的message類型
	required string sername = 1;
	optional int32 age = 2;
	repeated int32 height = 3;
}
//定義服務類型,用作rpc通訊
service SearchService {
    //rpc 服務的函數名 (傳入參數)返回(返回參數)
    //傳入和返回參數,需要使用proto中定義的message
    rpc Search (SearchRequest) returns (SearchResponse);
}

生成指定語言的proto文件

protoc內置9中語言的編譯插件,如下

我們以go為例。protoc沒有內置go的編譯器,需要引入外部插件protoc-gen-go

我們使用命令

go get github.com/golang/protobuf/protoc-gen-go

下載并編譯了包,包中有main.go文件,所以在GOPATH/bin目錄下生成了可執行文件protoc-gen-go.exe,這個文件就是我們在protoc中用到的插件

我們也可以自定義自己的插件。插件名必須以protoc-gen-插件名.exe命名

在調用時以:插件_out=參數 調用,之后回講到如何制作插件

我們以go_out插件為例,執行命令

命令行后的第一個參數為輸出目錄。但是我們已經在go_package中指定了目錄,所以第一個參數是無效的,填當前目錄.即可。第二個參數是,要編譯的文件路徑和文件名(以命令執行目錄為當前目錄的相對路徑)

protoc后可以追加1個或多個 -I=path 指定解析import指令時要在其中查找.proto文件的目錄,若文件沒有使用import,則不需要該參數

protoc --go_out=. proto_type/hello.proto

報錯

protoc默認會從環境變量path下的路徑中尋找插件,沒找到報錯。

查看發現path中只配置了go的安裝GOROOT目錄下的bin,而protoc-gen-go.exe在GOPATH的bin目錄下

我們把protoc-gen-go.exe復制到GOROOT的bin目錄下,即可。或者在環境變量path中添加gopath/bin目錄。再次執行命令即可

調用proto

import (
	"fmt"
	"github.com/golang/protobuf/proto"
)
func main() {
	//定義一個要加密的變量
	msg := &protoc_type.User{
		Username:"zhangsan",
		Age:12,
	}
	//加密
	marshal,err := proto.Marshal(msg)
	if err!=nil{
		fmt.Println(err.Error())
	}else{
		fmt.Println(marshal)
	}
	//定義一個變量,用于接收解碼的值,類型必須用加密的值類型一樣
	unmarshal := &protoc_type.User{}
	//解碼
	err2 := proto.Unmarshal(marshal,unmarshal)
	if err2 == nil{
		fmt.Println(unmarshal)
		fmt.Println(unmarshal.Username)
		fmt.Printf("%T",unmarshal)
	}
}	

制作插件

我們上文中提到,生成go用到了一個protoc的外部插件。插件的工作原理其實就是讀取proto文件內容,并根據文件內容生成指定文件。文件中保存了定義的字段類型和方法之間的關系,以便編碼和解碼時使用

當我們在proto文件中定義了service,說明我們需要用到rpc服務。protoc內置的插件和一些官方的外部插件,只是提供了service中方法與參數類型的綁定,用于校驗調用方法時類型是否正確。在service中定義的方法,我們需要自己創建。

當方法較多時,我們需要打開proto文件,一一對應的創建func,效率不高也容易出錯。這種情況下,我們制作一個插件,讀取service內容,自動創建方法,方法內的代碼之后根據業務需求填充即可。

go官方提供了一個包,可以自動解析讀取命令行傳入proto文件,代碼如下

package main
import (
	"fmt"
	"strings"
	"google.golang.org/protobuf/compiler/protogen"
)
type rpc struct{}
func main() {
	g := rpc{}
	protogen.Options{}.Run(g.Generate)
}
// Generate generate service code
func (md *rpc) Generate(plugin *protogen.Plugin) error {
	//遍歷讀取的命令行中傳入的proto文件
	for _, f := range plugin.Files {
		//如果文件中沒有定義service,跳過
		if len(f.Services) == 0 {
			continue
		}
		//根據proto文件名,生成一個自定義的文件,保存該proto中定義的func
		fileName := f.GeneratedFilenamePrefix + ".svr.go"
		//把該文件保存在proto文件的所在目錄
		t := plugin.NewGeneratedFile(fileName, f.GoImportPath)
		//寫入文字
		t.P("http:// Code generated by protoc-gen-tinyrpc.")
		//寫入空行
		t.P()
		//寫入包名
		pkg := fmt.Sprintf("package %s", f.GoPackageName)
		t.P(pkg)
		t.P()
		//遍歷一個文件下所有service,自動生成方法
		for _, s := range f.Services {
			//插入注釋,定義service類型
			serviceCode := fmt.Sprintf(`%stype %s struct{}`,
				getComments(s.Comments), s.Desc.Name())
			t.P(serviceCode)
			t.P()
			//遍歷一個service下的方法,生成方法
			for _, m := range s.Methods {
				funcCode := fmt.Sprintf(`%sfunc(this *%s) %s(args *%s,reply *%s)error{
					// define your service ...
					return nil
				}
				`, getComments(m.Comments), s.Desc.Name(),
					m.Desc.Name(), m.Input.Desc.Name(), m.Output.Desc.Name())
				t.P(funcCode)
			}
		}
	}
	return nil
}
// getComments get comment details
func getComments(comments protogen.CommentSet) string {
	c := make([]string, 0)
	c = append(c, strings.Split(string(comments.Leading), "\n")...)
	c = append(c, strings.Split(string(comments.Trailing), "\n")...)
	res := ""
	for _, comment := range c {
		if strings.TrimSpace(comment) == "" {
			continue
		}
		res += "http://" + comment + "\n"
	}
	return res
}

執行 go install 編譯該文件,生成exe文件,文件名稱設置為protoc-gen-gofunc.exe。然后再次執行protoc命令,如下

protoc --go_out=. proto_type/hello.proto --gofunc_out=. proto_type/hello.proto

執行后,會在proto_type目錄下生成兩個文件,hello.pd.go, hello.srv.go

原文鏈接:https://blog.csdn.net/u012830303/article/details/126517355

欄目分類
最近更新