Golang 使用Protocol Buffer 案例
目錄
- 1. 前言
- 2. Protobuf 簡(jiǎn)介
- 2.1 Protobuf 優(yōu)點(diǎn)
- 2.2 Protobuf 缺點(diǎn)
- 2.3 Protobuf Golang 安裝使用
- 3. Protobuf 通訊案例
- 3.1 創(chuàng)建.proto協(xié)議文件
- 3.2 protobuf編解碼
- 3.3 socket通訊
- 4. Protobuf 基礎(chǔ)知識(shí)
- 4.1 簡(jiǎn)單模板
- 4.2 簡(jiǎn)單語(yǔ)法
- 4.3 注意事項(xiàng)
1. 前言
工作幾年了。ITDragon龍?的編程語(yǔ)言從熟悉的Java,到喜歡的Kotlin,最后到一言難盡的Golang。常用的數(shù)據(jù)交換格式也從xml到j(luò)son,最后到現(xiàn)在的protobuf。因?yàn)榈讓域?qū)動(dòng)對(duì)數(shù)據(jù)的采集非常頻繁,而且對(duì)數(shù)據(jù)的可讀性并沒(méi)有太高的要求。所以采用序列化體積更小、序列化和反序列化速度更快的protobuf。考慮到后期還會(huì)繼續(xù)深入使用protobuf,先插個(gè)眼,方便后期的使用和問(wèn)題排查。
2. Protobuf 簡(jiǎn)介
Google Protocol Buffer(簡(jiǎn)稱(chēng) Protobuf) 是Google 旗下的一款輕便高效的結(jié)構(gòu)化數(shù)據(jù)存儲(chǔ)格式,平臺(tái)無(wú)關(guān)、語(yǔ)言無(wú)關(guān)、可擴(kuò)展,可用于通訊協(xié)議和數(shù)據(jù)存儲(chǔ)等領(lǐng)域。適合用做數(shù)據(jù)存儲(chǔ)和作為不同應(yīng)用,不同語(yǔ)言之間相互通信的數(shù)據(jù)交換格式。
2.1 Protobuf 優(yōu)點(diǎn)
1.支持多種語(yǔ)言、跨平臺(tái),多平臺(tái)之間只需要維護(hù)一套proto協(xié)議文件(當(dāng)然還需要對(duì)應(yīng)的配置)。
2.序列化后是二進(jìn)制流,體積比Json和Xml小,適合數(shù)據(jù)量較大的傳輸場(chǎng)景。
3.序列化和反序列化速度很快,據(jù)說(shuō)比Json和Xml快20~100倍。(ITDragon龍?本地測(cè)過(guò),只有在數(shù)據(jù)達(dá)到一定量時(shí)才會(huì)有明顯的差距)
小結(jié):適合傳輸數(shù)據(jù)量較大,對(duì)響應(yīng)速度有要求的數(shù)據(jù)傳輸場(chǎng)景。
2.2 Protobuf 缺點(diǎn)
1.序列化后的數(shù)據(jù)不具備可讀性。
2.需要一定的額外開(kāi)發(fā)成本(.proto 協(xié)議文件),每次修改都需要重新生成協(xié)議文件。
3.應(yīng)用場(chǎng)景并沒(méi)有Json和Xml廣,相對(duì)于使用的工具也少。
小結(jié):自解釋性較差、通用性較差,不適合用于對(duì)基于文本的標(biāo)記文檔建模。
2.3 Protobuf Golang 安裝使用
step1:下載protobuf 編譯器protoc。下載地址?。
step2:下載對(duì)應(yīng)的文件。Windows系統(tǒng)直接將解壓后的protoc.exe放在GOPATH/bin目錄下(該目錄要放在環(huán)境變量中);Linux系統(tǒng)需要make編譯。
step3:安裝protobuf庫(kù)文件(因?yàn)閜rotoc并沒(méi)有直接支持go語(yǔ)言)。官方goprotobuf,執(zhí)行成功后GOPATH/bin目錄下會(huì)有protoc-gen-go.exe文件
go get github.com/golang/protobuf/proto go get github.com/golang/protobuf/protoc-gen-go據(jù)說(shuō)gogoprotobuf庫(kù)生成的代碼質(zhì)量和編解碼性能都比官方的goprotobuf庫(kù)強(qiáng),而且完全兼容官方的protobuf。ITDragon龍?我們當(dāng)然也不能放過(guò)它。
step4:安裝gogoprotobuf庫(kù)文件。執(zhí)行成功后GOPATH/bin目錄下會(huì)有protoc-gen-gofast.exe文件
go get github.com/gogo/protobuf/proto go get github.com/gogo/protobuf/gogoproto go get github.com/gogo/protobuf/protoc-gen-gofaststep5:使用protoc 生成go文件。先移步到xxx.proto文件所在目錄,再執(zhí)行以下任意一個(gè)命令
// 官方goprotobuf protoc --go_out=. *.proto // gogoprotobuf protoc --gofast_out=. *.protostep6:protoc是直接支持Java語(yǔ)言,下載后可以直接使用。
protoc xxx.proto --java_out=./3. Protobuf 通訊案例
這里用Golang分別實(shí)現(xiàn)socket的服務(wù)端和客戶端,最后通過(guò)protobuf進(jìn)行數(shù)據(jù)傳輸,實(shí)現(xiàn)一個(gè)簡(jiǎn)單案例。
3.1 創(chuàng)建.proto協(xié)議文件
1.創(chuàng)建一個(gè)簡(jiǎn)單的models.proto協(xié)議文件
syntax = "proto3"; package protobuf;message MessageEnvelope{int32 TargetId = 1;string ID = 2;bytes Payload = 3;string Type = 4; }2.通過(guò)protoc生成對(duì)應(yīng)的models.pb.go文件(這個(gè)文件內(nèi)容太多,可讀性也差,就不貼出來(lái)了)
protoc --gofast_out=. *.proto3.2 protobuf編解碼
package protobufimport ("fmt""github.com/golang/protobuf/proto""testing" )func TestProtocolBuffer(t *testing.T) {// MessageEnvelope是models.pb.go的結(jié)構(gòu)體oldData := &MessageEnvelope{TargetId: 1,ID: "1",Type: "2",Payload: []byte("ITDragon protobuf"),}data, err := proto.Marshal(oldData)if err != nil {fmt.Println("marshal error: ", err.Error())}fmt.Println("marshal data : ", data)newData := &MessageEnvelope{}err = proto.Unmarshal(data, newData)if err != nil {fmt.Println("unmarshal err:", err)}fmt.Println("unmarshal data : ", newData)} -----------打印結(jié)果----------- === RUN TestProtocolBuffer marshal data : [8 1 18 1 49 26 17 73 84 68 114 97 103 111 110 32 112 114 111 116 111 98 117 102 34 1 50] unmarshal data : TargetId:1 ID:"1" Payload:"ITDragon protobuf" Type:"2" --- PASS: TestProtocolBuffer (0.00s) PASS3.3 socket通訊
1.TCP Server端
func TestTcpServer(t *testing.T) {// 為突出重點(diǎn),忽略err錯(cuò)誤判斷addr, _ := net.ResolveTCPAddr("tcp4", "127.0.0.1:9000")listener, _ := net.ListenTCP("tcp4", addr)for {conn, _ := listener.AcceptTCP()go func() {for {buf := make([]byte, 512)_, _ = conn.Read(buf)newData := &MessageEnvelope{}_ = proto.Unmarshal(buf, newData)fmt.Println("server receive : ", newData)}}()} }2.TCP Client端
func TestTcpClient(t *testing.T) {// 為突出重點(diǎn),忽略err錯(cuò)誤判斷connection, _ := net.Dial("tcp", "127.0.0.1:9000")var targetID int32 = 1for {oldData := &MessageEnvelope{TargetId: targetID,ID: strconv.Itoa(int(targetID)),Type: "2",Payload: []byte(fmt.Sprintf("ITDragon protoBuf-%d", targetID)),}data, _ := proto.Marshal(oldData)_, _ = connection.Write(data)fmt.Println("client send : ", data)time.Sleep(2 * time.Second)targetID++} }4. Protobuf 基礎(chǔ)知識(shí)
這里記錄工作中常用知識(shí)點(diǎn)和對(duì)應(yīng)的注意事項(xiàng),詳細(xì)知識(shí)點(diǎn)可以通過(guò)官網(wǎng)查詢:https://developers.google.com/protocol-buffers/docs/proto3
4.1 簡(jiǎn)單模板
舉一個(gè)簡(jiǎn)單列子。不同的編程語(yǔ)言的語(yǔ)法差別主要體現(xiàn)在數(shù)據(jù)類(lèi)型的不同。
syntax = "proto3"; // 指定使用proto3語(yǔ)法 package protobuf; // 指定包名message MessageEnvelope{ // 定義一個(gè)消息模型uint32 TargetId = 1; // 定義一個(gè)無(wú)符號(hào)整數(shù)類(lèi)型string ID = 2; // 定義一個(gè)字符串類(lèi)型bytes Payload = 3; // 定義一個(gè)字節(jié)類(lèi)型MessageType Type = 4; // 定義一個(gè)枚舉類(lèi)型repeated Player Players = 5; // 定義一個(gè)集合對(duì)象類(lèi)型 }enum MessageType { // 定義一個(gè)枚舉類(lèi)型SYSTEM = 0; // 第一個(gè)枚舉值為零ALARM = 1; }message Player {... }4.2 簡(jiǎn)單語(yǔ)法
1.syntax?: 指定使用proto版本的語(yǔ)法,缺省是proto2。若使用syntax語(yǔ)法,則必須位于文件的非空非注釋的第一個(gè)行。若不指定proto3,卻使用了proto3的語(yǔ)法,則會(huì)報(bào)錯(cuò)。
2.package?: 指定包名。防止不同 .proto 項(xiàng)目間命名發(fā)生沖突。
3.message?: 定義消息類(lèi)型。
4.enum?: 定義枚舉類(lèi)型。第一個(gè)枚舉值設(shè)置為零。
5.repeated?: 表示被修飾的變量允許重復(fù),可以理解成集合、數(shù)組、切片。
6.map?: 待補(bǔ)充
7.Oneof?: 待補(bǔ)充
8.定義變量?: (字段修飾符) + 數(shù)據(jù)類(lèi)型 + 字段名稱(chēng) = 唯一的編號(hào)標(biāo)識(shí)符;
9.編號(hào)標(biāo)識(shí)符?:在message中,每個(gè)字段都有唯一的編號(hào)標(biāo)識(shí)符。用來(lái)在消息的二進(jìn)制格式中識(shí)別各個(gè)字段,一旦使用就不能夠再改變。[1,15]之內(nèi)的標(biāo)識(shí)符在編碼時(shí)占用一個(gè)字節(jié)。[16,2047]之內(nèi)的標(biāo)識(shí)符占用2個(gè)字節(jié)。
10.變量類(lèi)型:以下來(lái)源網(wǎng)絡(luò)整理
| double | float64 | double | double | double | float | |
| float | float32 | float | float | float | float | |
| int32 | 使用變長(zhǎng)編碼,對(duì)于負(fù)值的效率很低,如果你的域有可能有負(fù)值,請(qǐng)使用sint64替代 | int32 | int | int32 | int | int |
| uint32 | 使用變長(zhǎng)編碼 | uint32 | int | uint32 | uint | int/long |
| uint64 | 使用變長(zhǎng)編碼 | uint64 | long | uint64 | ulong | int/long |
| sint32 | 使用變長(zhǎng)編碼,這些編碼在負(fù)值時(shí)比int32高效的多 | int32 | int | int32 | int | int |
| sint64 | 使用變長(zhǎng)編碼,有符號(hào)的整型值。編碼時(shí)比通常的int64高效。 | int64 | long | int64 | long | int/long |
| fixed32 | 總是4個(gè)字節(jié),如果數(shù)值總是比總是比228大的話,這個(gè)類(lèi)型會(huì)比uint32高效。 | uint32 | int | uint32 | uint | int |
| fixed64 | 總是8個(gè)字節(jié),如果數(shù)值總是比總是比256大的話,這個(gè)類(lèi)型會(huì)比uint64高效。 | uint64 | long | uint64 | ulong | int/long |
| sfixed32 | 總是4個(gè)字節(jié) | int32 | int | int32 | int | int |
| sfixed64 | 總是8個(gè)字節(jié) | int64 | long | int64 | long | int/long |
| bool | bool | boolean | bool | bool | bool | |
| string | 一個(gè)字符串必須是UTF-8編碼或者7-bit ASCII編碼的文本。 | string | String | string | string | str /unicode |
| bytes | 可能包含任意順序的字節(jié)據(jù)。 | []byte | ByteString | string | ByteString | str |
4.3 注意事項(xiàng)
1.整數(shù)數(shù)據(jù)類(lèi)型區(qū)分好有符號(hào)和無(wú)符號(hào)類(lèi)型,建議少用萬(wàn)金油式的int32。
2.將可能頻繁使用的字段設(shè)置在[1,15]之內(nèi),也要注意預(yù)留幾個(gè),方便后期添加。
總結(jié)
以上是生活随笔為你收集整理的Golang 使用Protocol Buffer 案例的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Spring Boot(2.1.2.RE
- 下一篇: go protobuf tcp 粘包处理