天天看點

golang使用protocol buffer案例

作者:幹飯人小羽

Protobuf 簡介

Google Protocol Buffer(簡稱 Protobuf) 是Google 旗下的一款輕便高效的結構化資料存儲格式,平台無關、語言無關、可擴充,可用于通訊協定和資料存儲等領域。适合用做資料存儲和作為不同應用,不同語言之間互相通信的資料交換格式。

2.1 Protobuf 優點

1.支援多種語言、跨平台,多平台之間隻需要維護一套proto協定檔案(當然還需要對應的配置)。

2.序列化後是二進制流,體積比Json和Xml小,适合資料量較大的傳輸場景。

3.序列化和反序列化速度很快,據說比Json和Xml快20~100倍。(ITDragon龍 本地測過,隻有在資料達到一定量時才會有明顯的差距)

小結:适合傳輸資料量較大,對響應速度有要求的資料傳輸場景。

2.2 Protobuf 缺點

1.序列化後的資料不具備可讀性。

2.需要一定的額外開發成本(.proto 協定檔案),每次修改都需要重新生成協定檔案。

3.應用場景并沒有Json和Xml廣,相對于使用的工具也少。

小結:自解釋性較差、通用性較差,不适合用于對基于文本的标記文檔模組化。

2.3 Protobuf Golang 安裝使用

step1:下載下傳protobuf 編譯器protoc。下載下傳位址 。

step2:下載下傳對應的檔案。Windows系統直接将解壓後的protoc.exe放在GOPATH/bin目錄下(該目錄要放在環境變量中);Linux系統需要make編譯。

step3:安裝protobuf庫檔案(因為protoc并沒有直接支援go語言)。官方goprotobuf,執行成功後GOPATH/bin目錄下會有protoc-gen-go.exe檔案

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

據說gogoprotobuf庫生成的代碼品質和編解碼性能都比官方的goprotobuf庫強,而且完全相容官方的protobuf。ITDragon龍 我們當然也不能放過它。

step4:安裝gogoprotobuf庫檔案。執行成功後GOPATH/bin目錄下會有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-gofast
           

step5:使用protoc 生成go檔案。先移步到xxx.proto檔案所在目錄,再執行以下任意一個指令

// 官方goprotobuf
protoc --go_out=. *.proto
// gogoprotobuf
protoc --gofast_out=. *.proto
           

step6:protoc是直接支援Java語言,下載下傳後可以直接使用。

protoc xxx.proto --java_out=./
           

3. Protobuf 通訊案例

這裡用Golang分别實作socket的服務端和用戶端,最後通過protobuf進行資料傳輸,實作一個簡單案例。

3.1 建立.proto協定檔案

1.建立一個簡單的models.proto協定檔案

syntax = "proto3";
package protobuf;

message MessageEnvelope{
    int32 TargetId = 1;
    string ID = 2;
    bytes Payload = 3;
    string Type = 4;
}
           

2.通過protoc生成對應的models.pb.go檔案(這個檔案内容太多,可讀性也差,就不貼出來了)

protoc --gofast_out=. *.proto
           

3.2 protobuf編解碼

package protobuf

import (
	"fmt"
	"github.com/golang/protobuf/proto"
	"testing"
)

func TestProtocolBuffer(t *testing.T) {
	// MessageEnvelope是models.pb.go的結構體
	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)

}
-----------列印結果-----------
=== 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)
PASS
           

3.3 socket通訊

1.TCP Server端

func TestTcpServer(t *testing.T) {
	// 為突出重點,忽略err錯誤判斷
	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) {
	// 為突出重點,忽略err錯誤判斷
	connection, _ := net.Dial("tcp", "127.0.0.1:9000")
	var targetID int32 = 1
	for {
		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 基礎知識

這裡記錄工作中常用知識點和對應的注意事項,詳細知識點可以通過官網查詢:https://developers.google.com/protocol-buffers/docs/proto3

4.1 簡單模闆

舉一個簡單列子。不同的程式設計語言的文法差别主要展現在資料類型的不同。

syntax = "proto3"; 					// 指定使用proto3文法
package protobuf;  					// 指定包名

message MessageEnvelope{ 			// 定義一個消息模型
    uint32 TargetId = 1; 			// 定義一個無符号整數類型
    string ID = 2;					// 定義一個字元串類型
    bytes Payload = 3;				// 定義一個位元組類型
    MessageType Type = 4;			// 定義一個枚舉類型
	repeated Player Players = 5;	// 定義一個集合對象類型
}

enum MessageType {					// 定義一個枚舉類型
	SYSTEM = 0;						// 第一個枚舉值為零
	ALARM = 1;
}

message Player {
	...
}
           

4.2 簡單文法

1.syntax : 指定使用proto版本的文法,預設是proto2。若使用syntax文法,則必須位于檔案的非空非注釋的第一個行。若不指定proto3,卻使用了proto3的文法,則會報錯。

2.package : 指定包名。防止不同 .proto 項目間命名發生沖突。

3.message : 定義消息類型。

4.enum : 定義枚舉類型。第一個枚舉值設定為零。

5.repeated : 表示被修飾的變量允許重複,可以了解成集合、數組、切片。

6.map : 待補充

7.Oneof : 待補充

8.定義變量 : (字段修飾符) + 資料類型 + 字段名稱 = 唯一的編号辨別符;

9.編号辨別符 :在message中,每個字段都有唯一的編号辨別符。用來在消息的二進制格式中識别各個字段,一旦使用就不能夠再改變。[1,15]之内的辨別符在編碼時占用一個位元組。[16,2047]之内的辨別符占用2個位元組。

10.變量類型:以下來源網絡整理

.proto Notes Go Java C++ C# Python
double float64 double double double float
float float32 float float float float
int32 使用變長編碼,對于負值的效率很低,如果你的域有可能有負值,請使用sint64替代 int32 int int32 int int
uint32 使用變長編碼 uint32 int uint32 uint int/long
uint64 使用變長編碼 uint64 long uint64 ulong int/long
sint32 使用變長編碼,這些編碼在負值時比int32高效的多 int32 int int32 int int
sint64 使用變長編碼,有符号的整型值。編碼時比通常的int64高效。 int64 long int64 long int/long
fixed32 總是4個位元組,如果數值總是比總是比228大的話,這個類型會比uint32高效。 uint32 int uint32 uint int
fixed64 總是8個位元組,如果數值總是比總是比256大的話,這個類型會比uint64高效。 uint64 long uint64 ulong int/long
sfixed32 總是4個位元組 int32 int int32 int int
sfixed64 總是8個位元組 int64 long int64 long int/long
bool bool boolean bool bool bool
string 一個字元串必須是UTF-8編碼或者7-bit ASCII編碼的文本。 string String string string str /unicode
bytes 可能包含任意順序的位元組據。 []byte ByteString string ByteString str

4.3 注意事項

1.整數資料類型區分好有符号和無符号類型,建議少用萬金油式的int32。

2.将可能頻繁使用的字段設定在[1,15]之内,也要注意預留幾個,友善後期添加。