文章目錄
標題 | 版本號 | 作者 | 技術棧版本號 | IDE | 官網 | |
---|---|---|---|---|---|---|
Protocol Buffers(protobuf)學習_v0.0.1_持續更新。。。 | v0.0.1 | 飛豺 | 8416837 | pb3.10.1 golang1.13.4 |
LiteIDE | protobuf golang |
尊重版權,轉載請註明出處:https://blog.csdn.net/cc007cc009/article/details/103107725
概念
- protocol buffers 是一種語言無關、平臺無關、可擴展的序列化結構數據的方法,它可用於(數據)通信協議、數據存儲等。它的同行是風行一時的xml以及json.二進制。
- 用途較廣。如區塊鏈相關開發,本章亦有相關實踐,詳情見:調用區塊鏈的gRPC;消息隊列,如騰訊開源的TubeMQ;
- 體積較小,節省資源。
原理
數據結構
- 數字爲key,更節省資源;
- 定義簡單,維護成本低;
- 能被編譯,開發更高效;
操作步驟
- 第一步,定義消息結構體,即.proto文件;
- 第二步,使用protobuf編譯器編譯.proto文件,編譯環境爲集成開發工具或者命令行終端;
- 第三步,使用C++/Java、c sharp、nodejs以及golang等對應的protobuf API讀寫消息,本文使用golang開發gRPC;
開發環境的準備
Win
- 下載 protoc-3.10.1-win64.zip
- 解壓,把它的根目錄配置到環境變量配置:Path
- 檢驗。
protoc --version
- 新建robot.proto
syntax = "proto3"; // 版本號
message Robot { // 類結構體
int32 id = 999999; // 數字作爲key,省空間。不一定要連續。
string name = 2;
string level = 3;
}
- 編譯。
protoc --go_out=. --python_out=. robot.proto # 編譯成Golang、Python模型
執行上個命令,報錯了--go_out: protoc-gen-go: Plugin failed with status code 1.
看來缺少插件,需安裝golang和插件。
- 安裝golang
接上文問題。下載 https://studygolang.com/dl/golang/go1.13.4.windows-amd64.msi,因爲開發機是Win OS.Linux安裝見區塊鏈文檔。安裝完畢,發現已經自動配置了環境變量:,
配置環境變量,GOROOT,即golang根目錄,也即msi文件的安裝目錄;GOPATH,即工作空間;注意,win10環境,GOPATH配置到當前用戶;
檢驗一下↓
- 安裝插件
go get -u github.com/golang/protobuf/protoc-gen-go # 使用go安裝protoc-gen-go插件,該插件編譯protobuf時有用到。因網絡問題有可能失敗
下載完畢後,可以在C:\Users\Administrator\go\bin
裏面找到protoc-gen-go.exe
,爲什麼能找到,——因爲這是默認的GOPATH目錄。但是自定義的GOPATH裏面沒找到這個文件,說明自定義配置不OK.——這樣,插件也裝好了。
- 重新編譯proto文件,成功。
↑生成了兩個文件,go、py.
Ubuntu環境
sudo apt-get install autoconf automake libtool curl make g++ unzip
sudo apt-get install autoconf # 安裝自動配置
git clone https://github.com/google/protobuf.git # 下載源碼
git clone https://gitee.com/githubmirror/protobuf # 國內鏡像更快
cd protobuf
git submodule update --init --recursive
bash autogen.sh # 找不到這個文件
./configure # 找不到這個文件
sudo apt install golang-go # 安裝golang
go version # 查看是否成功
# 設置GOPATH
make
make check
sudo make install
make報錯:
# 顯示
apt-cache showpkg libcurl3-gnutls
# 安裝:
sudo apt-get install libcurl3-gnutls=7.47.0-1ubuntu2
還有沒安裝go也會報命令錯誤
gopath配置:
sudo gedit ~/.profile
export GOROOT="/usr/lib/go" # 引號內設置爲你自己的go安裝目錄
export GOBIN=$GOROOT/bin
export GOPATH="/home/project/gopath" # 引號內設置爲自己的go項目的工作區間
export PATH=$PATH:$GOPATH/bin # 原路徑後用冒號連接新路徑
source ~/.profile
解決下載慢:
export GOPROXY=https://mirrors.aliyun.com/goproxy/
安裝protoc:
go get -u github.com/golang/protobuf/protoc-gen-go
apt install protobuf-compiler
apt-cache madison protobuf-compiler # 查看存在的版本號
版本號不合適,重新安裝:
wget https://github.com/google/protobuf/releases/download/v2.5.0/protobuf-2.5.0.tar.gz
tar -xzvf protobuf-2.5.0.tar.gz
pushd protobuf-2.5.0 && sudo ./configure --prefix=/usr/local && sudo make && sudo make install && popd
安裝成功後:
# 報錯:protoc: error while loading shared libraries: libprotoc.so.8: cannot open shared object file: No such file or directory
export LD_LIBRARY_PATH=/usr/local/lib
相關工具
GO IDE
golang LiteIDE
# native compiler windows 386
GOROOT=G:\Program Files (x86)\golang # go安裝目錄
GOBIN=G:\Program Files (x86)\golang\bin
#GOARCH=386
#GOOS=windows
#CGO_ENABLED=1
PATH=c:\mingw32\bin;%GOROOT%\bin;%PATH%
#LITEIDE_GDB=gdb
LITEIDE_MAKE=mingw32-make
LITEIDE_TERM=%COMSPEC%
LITEIDE_TERMARGS=
LITEIDE_EXEC=%COMSPEC%
LITEIDE_EXECOPT=/C
保存成功的響應日誌:
23:18:34 LiteEnv: reset system environment for "go env"
23:18:34 GolangCode: gocode set lib-path "C:\Users\cc\go"
23:18:34 LiteBuild: go environment changed
23:18:34 GolangPackage: Found go bin at C:\Go\bin\go.exe
23:18:34 GolangPackage: GOROOT=C:\Go
23:18:34 GolangPackage: GOPATH=C:\Users\cc\go
23:18:34 GolangCode: go environment changed
23:18:34 GolangCode: Found gocode at C:/dev/tools/ide/liteidex36.2.windows-qt5.9.5/liteide/bin/gocode.exe
點擊 -> 工具 => 管理GOPATH.選擇Win環境變量配置的值。
- 新建。點擊新建項目,選擇GOPATH、模板。如↓
- 下述代碼數據操作部分運行成功的保證是:成功從github.com下載了依賴、有數據庫
// ccgotest project main.go
package main
import (
"database/sql"
"fmt"
"log"
_ "github.com/go-sql-driver/mysql"
)
func main() {
var i int = 1
i += 2
j := i + 3
for {
if j < i {
break
}
fmt.Println("Hello World!")
j--
fmt.Println("j === ", j, ",i === ", i)
}
// DB測試
db, err := sql.Open("mysql", "root:pass@tcp(localhost:3306)/eladmin?charset=utf8")
if err != nil {
panic(err)
}
// DB操作 - start
username1 := "test"
stmt, err := db.Prepare("SELECT id FROM user WHERE username = ?")
if err != nil {
log.Println("數據庫錯誤")
log.Fatal(err)
}
rows, err := stmt.Query(username1)
fmt.Println(err, rows)
// process rows
for rows.Next() {
// var username string
// var id int32
var value string
if err := rows.Scan(&value); err == nil {
fmt.Println("結果")
fmt.Println(value)
}
//fmt.Printf("name:%s ,id:is %d\n", name, id)
}
// DB操作 - end
defer db.Close()
}
實踐
Demo
Java版 - IDEA版 - 也可以使用命令行
- 安裝相關插件:Protobuf Support、Protobuf Highlight(編輯器高亮,選裝),重啓.
- 配置。pom.xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cc</groupId>
<artifactId>cc-test-protobuf</artifactId>
<version>1.0-SNAPSHOT</version>
<name>cc-test-protobuf</name>
<!-- FIXME change it to the project's website -->
<url>http://www.cc.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>3.4.0</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<extensions>
<extension>
<groupId>kr.motd.maven</groupId>
<artifactId>os-maven-plugin</artifactId>
<version>1.4.1.Final</version>
</extension>
</extensions>
<plugins>
<plugin>
<groupId>org.xolstice.maven.plugins</groupId>
<artifactId>protobuf-maven-plugin</artifactId>
<version>0.5.0</version>
<configuration>
<protocArtifact>
com.google.protobuf:protoc:3.1.0:exe:${os.detected.classifier}
</protocArtifact>
<pluginId>grpc-java</pluginId>
</configuration>
<executions>
<execution>
<goals>
<goal>compile</goal>
<goal>compile-custom</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-jar-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
<!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
<plugin>
<artifactId>maven-site-plugin</artifactId>
<version>3.7.1</version>
</plugin>
<plugin>
<artifactId>maven-project-info-reports-plugin</artifactId>
<version>3.0.0</version>
</plugin>
</plugins>
</pluginManagement>
</build>
</project>
- 模型。新建person.proto
syntax = "proto3"; // 版本號。與proto2差別不大
option java_package = "com.cc.model";
option java_outer_classname = "PersonModel";
message Person { // 類結構體
int32 id = 1; // 結構體屬性
string name = 2; // 數字作爲key,省空間。不一定要連續
string email = 3;
}
- 編譯
注意:com.google.protobuf:protoc:3.1.0:exe
如果下載失敗,則拷貝到本地Maven庫;
操作系統種類變量:${os.detected.classifier}
:windows-x86_64等種類
並沒編譯成功,報錯:\src\main\proto does not exist.
修正文件目錄爲proto
,再次編譯。
這次編譯OK.
,手動拷貝類到java目錄 - 測試
test.java
@Test
public void testProtobuf() throws InvalidProtocolBufferException {
PersonModel.Person.Builder builder = PersonModel.Person.newBuilder();
builder.setId(1)
.setName("Shanks")
.setEmail("[email protected]");
PersonModel.Person person = builder.build();
System.out.println("before:");
System.out.println(person);
System.out.println("===Person Byte:===");
for (byte b : person.toByteArray()) {
System.out.print(b);
}
System.out.println();
System.out.println("================");
byte[] byteArray = person.toByteArray();
PersonModel.Person p2 = PersonModel.Person.parseFrom(byteArray);
System.err.println("after id:" + p2.getId());
System.err.println("after name:" + p2.getName());
System.err.println("after email:" + p2.getEmail());
assertTrue(true);
}
output log
before:
id: 1
name: "Shanks"
email: "[email protected]"
===Person Byte:===
8118683104971101071152615107575757575757641071111024699111109
================
after id:1
after name:Shanks
after email:k999999@kof.com
gRPC
golang版
基礎包安裝及編譯錯誤解決
- 基礎命令
go get -u github.com/golang/protobuf/proto # golang protobuf 庫 ,上文已裝過
go get -u github.com/golang/protobuf/protoc-gen-go # protoc --go_out 工具
go get google.golang.org/grpc # 安裝grpc # 默認gopath路徑:C:\Users\Administrator\go\src\github.com,不含\src\github.com
# 如果下不下來,請使用下述命令
cd $GOPATH/google.golang.org
git clone https://github.com/grpc/grpc-go # 選擇go版本的grpc,也可選擇碼雲極速下載https://gitee.com/mirrors/grpc,但是這個不是go版本的,注意甄別
go install google.golang.org/grpc-go
# 將grpc-go改名爲grpc
git clone https://github.com/grpc/grpc # 這個不是go語言的版本,不要選
- golang.org/x/net 安裝方法
mkdir -p $GOPATH/src/golang.org/x/
$cd $GOPATH/src/golang.org/x/
$git clone https://github.com/golang/net.git net
$go install net
- 安裝之後,build,之前幾個報錯解決了,但是又有了新的報錯:
..\..\golang.org\x\net\idna\idna10.0.0.go:25:2: cannot find package "golang.org/x/text/secure/bidirule" in any of:
解決辦法:
cd golang.org
git clone https://github.com/golang/text # 很簡單,因爲找不到這個包,那就下載它
cd google.golang.org
git clone https://github.com/googleapis/go-genproto
mv go-genproto/ genproto/
編碼
- 編寫參數:helloword.proto
syntax = "proto3";
option objc_class_prefix = "HLW";
package proto;
// The greeting service definition.
service Greeter {
// Sends a greeting
rpc SayHello (HelloRequest) returns (HelloReply) {}
}
// The request message containing the user's name.
message HelloRequest {
string name = 1;
}
// The response message containing the greetings
message HelloReply {
string message = 1;
}
- 編譯
protoc --go_out=plugins=grpc:. helloworld.proto
,生成*.pb.proto文件以備後用。 - 客戶端:
package main
import (
"log"
"os"
pb "test/grpc-go-test/proto"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
const (
address = ":8999"
)
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewGreeterClient(conn) // 構造器
name := "Yuzhi"
if len(os.Args) > 1 {
name = os.Args[1]
}
r, err := c.SayHello(context.Background(), &pb.HelloRequest{Name: name})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Println(r.Message)
}
- 服務端
package main
import (
"log"
"net"
pb "test/grpc-go-test/proto"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
const (
PORT = ":8999" // 本地地址
)
type server struct{}
func (s *server) SayHello(ctx context.Context, in *pb.HelloRequest) (*pb.HelloReply, error) {
log.Println("request: ", in.Name)
return &pb.HelloReply{Message: "Hello123 " + in.Name}, nil
}
func main() {
lis, err := net.Listen("tcp", PORT)
if err != nil {
log.Fatalf("failed to listen: %v", err)
}
s := grpc.NewServer()
pb.RegisterGreeterServer(s, &server{})
log.Println("gRPC服務已開啓")
s.Serve(lis)
}
- 分別啓動服務端、客戶端,點擊LiteIDE的BR按鈕運行,調用成功。
// server
2019/11/26 15:08:16 rpc服務已經開啓
2019/11/26 15:09:03 request: Yuzhi
// client
E:/gopath/gopath/src/test/grpc-client-test/grpc-client-test.exe [E:/gopath/gopath/src/test/grpc-client-test]
2019/11/26 15:09:03 Hello123 Yuzhi
調用區塊鏈的gRPC
編碼
proto(節選)
syntax = "proto3";
import "google/api/annotations.proto";
import "chainedbft.proto";
package proto;
// ...
rpc QueryACL(AclStatus) returns (AclStatus) {
option (google.api.http) = {
post : "/v1/query_acl"
body : "*"
};
}
// ...
// 查詢Acl
message AclStatus {
Header header = 1; // 使用插件可以省略數字key
string bcname = 2;
string accountName = 3;
string contractName = 4;
string methodName = 5;
bool confirmed = 6;
Acl acl = 7;
}
// ...
gRPC客戶端
首先你要有區塊鏈節點,見區塊鏈教程。
- 代碼
package main
import (
"fmt"
"log"
pb "test/grpc-client-xchain/proto"
"golang.org/x/net/context"
"google.golang.org/grpc"
)
const (
address = "ip:37101" // 區塊鏈節點
)
func main() {
conn, err := grpc.Dial(address, grpc.WithInsecure())
if err != nil {
log.Fatalf("did not connect: %v", err)
}
defer conn.Close()
c := pb.NewXchainClient(conn) // 構造器
// 查詢交易
bcname := "xuper"
// bcname := `xuper`
txid := "1130b45b1d1b953ddb1098878a1f87d6cdc4e8cb4ada9231fdeee241e85a451e"
// txid := `1130b45b1d1b953ddb1098878a1f87d6cdc4e8cb4ada9231fdeee241e85a451e`
// txid := `b29e4013948c5f24702e9c36c02548c4ae2162be8cf9cf0c38e18ff278bc0d59`
var txidBytes = []byte(txid)
fmt.Println(`byte數組`, txidBytes)
// r, err := c.PostTx(context.TODO(), &pb.TxStatus{Bcname: bcname, Txid: txidBytes})
r, err := c.QueryTx(context.Background(), &pb.TxStatus{Bcname: bcname, Txid: txidBytes})
if err != nil {
log.Fatalf("could not greet: %v", err)
}
log.Println(r)
// 查詢餘額
address := "XC1234560123456789@xuper"
// address := "Umu6Yk6mxiyZFXQhw8zo1r3KpM9nH9d8r"
r1, err1 := c.GetBalance(context.Background(), &pb.AddressStatus{Address: address})
if err1 != nil {
log.Fatalf("could not greet1: %v", err1)
}
log.Println(r1)
// 查詢ACL
accountName := `XC1234560123456789@xuper`
r2, err2 := c.QueryACL(context.Background(), &pb.AclStatus{Bcname: bcname, AccountName: accountName})
if err2 != nil {
log.Fatalf("could not greet2: %v", err2)
}
log.Println(r2)
}
- 響應成功(節選)
2019/11/26 18:12:19 header:<> confirmed:true acl:<pm:<rule:SIGN_THRESHOLD acceptValue:1 > aksWeight:<key:"Umu6Yk6mxiyZFXQhw8zo1r3KpM9nH9d8r" value:1 > >
成功: 進程退出代碼 0.