Golang安裝和使用protocol-buffer流程介紹
前言
protocol buffer是Google發(fā)布的一種獨(dú)立的數(shù)據(jù)交換格式,類似于json,用于數(shù)據(jù)的序列化和解析。不同點(diǎn)是不能直接在各編程語言中使用,需要先在一個proto文件中定義需要傳輸?shù)臄?shù)據(jù)格式,然后使用proto工具把proto文件編譯成想要的語言,如java、go、php等。然后在代碼中,使用語言對應(yīng)的protocol buffer包調(diào)用proto工具生成的文件,完成數(shù)據(jù)的序列化
安裝protoc編譯工具
首先安裝protoc編譯工具,在http://github.com/google/protobuf/releases,根據(jù)自己的系統(tǒng)選擇包,下載后解壓。我是win10 64,所以選擇protoc-21.4-win64.zip,解壓后放在了E盤。
添加環(huán)境變量,讓系統(tǒng)命令可以識別protoc命令。
配置環(huán)境變量
PB_PATH=E:\protoc-3.21.4-win64
PATH=%PB_PATH%\bin
添加后,在命令行執(zhí)行 protoc,返回信息,表示安裝成功
編寫proto文件
創(chuàng)建一個user.proto文件,該文件下可以定義一些user相關(guān)的需要加密的字段
//引入其他proto文件,就可以使用該文件中定義的message類型,定義本文件中的message下的字段 import "myproject/other_protos.proto"; // 指定的當(dāng)前proto語法的版本 syntax = "proto3"; // 指定生成出來的文件的package,用來避免同一個文件調(diào)同名的消息時(shí)沖突 // 包說明符對生成代碼的影響取決于您選擇的語言,php會生成命名空間,go會生成package package service; //根據(jù)編程語言不同,參數(shù)名不同 //下面代碼指定了生成go文件的生成目錄,文件的包名。不設(shè)置包名時(shí),包名默認(rèn)為go文件所在目錄名。會覆蓋package設(shè)置的go的包名 //路徑以執(zhí)行命令的目錄為當(dāng)前目錄,尋找相對路徑 option go_package="./;service"; //定義消息類型,通過關(guān)鍵字message字段指定的,消息就是需要傳輸?shù)臄?shù)據(jù)格式的定義 message User { // required必傳;optional 可選;repeated 可重復(fù)初入。不寫前綴默認(rèn)為required // 字段名后的數(shù)字不是默認(rèn)值,是標(biāo)識。標(biāo)識號是[0,2^29-1]范圍內(nèi)的一個整數(shù)。[1-15]內(nèi)的標(biāo)識號在編碼時(shí)只占用一個字節(jié),包含標(biāo)識符和字段類型,[16-2047]之間的標(biāo)識符占用2個字節(jié)。建議為頻繁出現(xiàn)的字段使用[1-15]間的標(biāo)識符。 //字段類型除了除了常規(guī)類型,也可使用import引入文件中定義的message類型 required string sername = 1; optional int32 age = 2; repeated int32 height = 3; } //定義服務(wù)類型,用作rpc通訊 service SearchService { //rpc 服務(wù)的函數(shù)名 (傳入?yún)?shù))返回(返回參數(shù)) //傳入和返回參數(shù),需要使用proto中定義的message rpc Search (SearchRequest) returns (SearchResponse); }
生成指定語言的proto文件
protoc內(nèi)置9中語言的編譯插件,如下
我們以go為例。protoc沒有內(nèi)置go的編譯器,需要引入外部插件protoc-gen-go
我們使用命令
go get github.com/golang/protobuf/protoc-gen-go
下載并編譯了包,包中有main.go文件,所以在GOPATH/bin目錄下生成了可執(zhí)行文件protoc-gen-go.exe,這個文件就是我們在protoc中用到的插件
我們也可以自定義自己的插件。插件名必須以protoc-gen-插件名.exe命名
在調(diào)用時(shí)以:插件_out=參數(shù) 調(diào)用,之后回講到如何制作插件
我們以go_out插件為例,執(zhí)行命令
命令行后的第一個參數(shù)為輸出目錄。但是我們已經(jīng)在go_package中指定了目錄,所以第一個參數(shù)是無效的,填當(dāng)前目錄.即可。第二個參數(shù)是,要編譯的文件路徑和文件名(以命令執(zhí)行目錄為當(dāng)前目錄的相對路徑)
protoc后可以追加1個或多個 -I=path 指定解析import指令時(shí)要在其中查找.proto文件的目錄,若文件沒有使用import,則不需要該參數(shù)
protoc --go_out=. proto_type/hello.proto
報(bào)錯
protoc默認(rèn)會從環(huán)境變量path下的路徑中尋找插件,沒找到報(bào)錯。
查看發(fā)現(xiàn)path中只配置了go的安裝GOROOT目錄下的bin,而protoc-gen-go.exe在GOPATH的bin目錄下
我們把protoc-gen-go.exe復(fù)制到GOROOT的bin目錄下,即可?;蛘咴诃h(huán)境變量path中添加gopath/bin目錄。再次執(zhí)行命令即可
調(diào)用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的外部插件。插件的工作原理其實(shí)就是讀取proto文件內(nèi)容,并根據(jù)文件內(nèi)容生成指定文件。文件中保存了定義的字段類型和方法之間的關(guān)系,以便編碼和解碼時(shí)使用
當(dāng)我們在proto文件中定義了service,說明我們需要用到rpc服務(wù)。protoc內(nèi)置的插件和一些官方的外部插件,只是提供了service中方法與參數(shù)類型的綁定,用于校驗(yàn)調(diào)用方法時(shí)類型是否正確。在service中定義的方法,我們需要自己創(chuàng)建。
當(dāng)方法較多時(shí),我們需要打開proto文件,一一對應(yīng)的創(chuàng)建func,效率不高也容易出錯。這種情況下,我們制作一個插件,讀取service內(nèi)容,自動創(chuàng)建方法,方法內(nèi)的代碼之后根據(jù)業(yè)務(wù)需求填充即可。
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 } //根據(jù)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 }
執(zhí)行 go install 編譯該文件,生成exe文件,文件名稱設(shè)置為protoc-gen-gofunc.exe。然后再次執(zhí)行protoc命令,如下
protoc --go_out=. proto_type/hello.proto --gofunc_out=. proto_type/hello.proto
執(zhí)行后,會在proto_type目錄下生成兩個文件,hello.pd.go, hello.srv.go
到此這篇關(guān)于Golang安裝和使用protocol-buffer方法介紹的文章就介紹到這了,更多相關(guān)Golang protocol-buffer內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Golang實(shí)現(xiàn)Md5校驗(yàn)的示例代碼
本文主要介紹了Golang實(shí)現(xiàn)Md5校驗(yàn)的示例代碼,要求接收方需要文件的md5值,和接收到的文件做比對,以免文件不完整,但引起bug,下面就一起來解決一下2024-08-08Go語言設(shè)計(jì)模式之結(jié)構(gòu)型模式
本文主要聚焦在結(jié)構(gòu)型模式(Structural Pattern)上,其主要思想是將多個對象組裝成較大的結(jié)構(gòu),并同時(shí)保持結(jié)構(gòu)的靈活和高效,從程序的結(jié)構(gòu)上解決模塊之間的耦合問題2021-06-06gin框架Context如何獲取Get?Query?Param函數(shù)數(shù)據(jù)
這篇文章主要為大家介紹了gin框架Context?Get?Query?Param函數(shù)獲取數(shù)據(jù),有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03