Protocol Buffers - 快速指南



Protocol Buffers - 簡介

在深入瞭解 Protocol Buffer 之前,讓我們先簡單瞭解一下序列化,因為 Protocol Buffer 就是用來做序列化的。

什麼是序列化和反序列化?

序列化是指將一個物件(任何語言的物件)轉換為位元組,並將其儲存在永續性記憶體系統中的過程。這個記憶體系統可以是磁碟上的檔案、訊息佇列或資料庫。序列化物件的主要目的是為了能夠重用資料,並在同一臺或不同的機器上重新建立該物件。在反序列化中,我們將儲存的位元組轉換回物件。

為什麼我們需要序列化和反序列化?

雖然還有其他一些用例,但最基本和最重要的用例是它提供了一種方法,可以透過網路將物件資料傳輸到不同的服務/機器等,然後重新建立物件以供進一步使用。透過 API、資料庫或訊息佇列傳輸物件資料需要將物件轉換為位元組,以便可以透過網路傳送。這就是序列化變得重要的原因。

在微服務架構中,應用程式被分解成小的服務,這些服務透過訊息佇列和 API 相互通訊。所有這些通訊都發生在網路上,這需要頻繁地將物件轉換為位元組,然後再轉換回物件。因此,在分散式環境中,序列化和反序列化變得非常關鍵。

為什麼選擇 Google Protocol Buffers?

Google Protocol Buffers 執行將物件序列化為位元組的過程,這些位元組可以傳輸到網路上。但也有其他一些庫和機制可以傳輸資料。

那麼,是什麼讓 Google Protocol Buffers 如此特別呢?以下是一些重要的特性:-

  • 語言獨立 - 多種語言都有 Protocol Buffers 庫,其中一些著名的庫包括 Java、Python、Go 等。因此,Java 物件可以由 Java 程式序列化為位元組,並且可以反序列化為 Python 物件。

  • 高效的資料壓縮 - 在微服務環境中,考慮到網路上會發生大量的通訊,因此我們傳送的資料儘可能簡潔至關重要。我們需要避免任何多餘的資訊,以確保資料能夠快速傳輸。Google Protocol Buffers 將此作為其重點關注領域之一。

  • 高效的序列化和反序列化 - 在微服務環境中,考慮到網路上會發生大量的通訊,因此序列化和反序列化的速度至關重要。Google Protocol Buffers 確保序列化和反序列化資料的速度儘可能快。

  • 易於使用 - Protocol Buffers 庫會自動生成序列化程式碼(我們將在接下來的章節中看到),並具有版本控制方案,以確保資料建立者和資料使用者可以擁有序列化定義的不同版本等。

Protocol Buffers 與其他方案比較 (XML/JSON/Java 序列化)

讓我們看看其他網路資料傳輸方式與 Protocol Buffers 相比如何。

特性 Protocol Buffers JSON XML
語言獨立
序列化資料大小 三者中最小的 小於 XML 三者中最大的
人類可讀 否,因為它使用單獨的編碼方案 是,因為它使用基於文字的格式 是,因為它使用基於文字的格式
序列化速度 三者中最快的 比 XML 快 三者中最慢的
資料型別支援 比其他兩者更豐富。支援複雜資料型別,如 Any、oneof 等。 支援基本資料型別 支援基本資料型別
支援演進模式

Protocol Buffers - 基本應用

概述

現在讓我們使用 Google Protocol Buffer 並瞭解它在一個簡單的問候應用中是如何工作的。在這個示例中,我們將建立一個簡單的應用程式,該應用程式將執行以下操作:-

  • 問候編寫者 -

    • 從使用者處獲取問候語和使用者名稱

    • 將上述資訊儲存在磁碟上的檔案中

  • 問候讀取者 -

    • 讀取我們在上述檔案中儲存的同一檔案

    • 將該資料轉換為物件並列印資料

Protocol Buffer 定義檔案

協議緩衝區“定義檔案”包含我們要序列化的資料的模式定義。資料儲存在副檔名為".proto" 的人類可讀檔案中。

讓我們將以下資料儲存在greeting.proto 中,我們將在第一個應用程式中使用它。

syntax = "proto3";
package tutorialspoint;
option java_package = "com.tutorialspoint.greeting";

message Greet {
   string greeting = 1;
   string username = 2;
}

理解每個構造

現在,讓我們仔細看看資料,並瞭解上面程式碼塊中每一行程式碼的作用。

syntax = "proto3";

這裡的syntax 表示我們使用的是哪個版本的 Protobuf。因此,我們使用的是最新的版本 3,因此模式可以使用對版本 3 有效的所有語法。

package tutorialspoint;

這裡的package 用於衝突解決,例如,如果我們有多個具有相同名稱的類/成員。

option java_package = "com.tutorialspoint.greeting";

此引數特定於 Java,即.proto 檔案中程式碼將自動生成的包。

message Greet

將要建立/重新建立的物件的基本類的名稱。

string greeting = 1;
string username = 2;

這些是Greet 類的屬性,以及資料型別和模式中標籤的位置。如果要新增新的標籤,它應該將“3”作為位置。請注意,此位置整數對於確保實際資料緊湊且有模式演進的餘地非常重要。

Protocol Buffer 程式碼生成

現在我們已經定義了,讓我們安裝“proto”二進位制檔案,我們將使用它來自動生成上面Greet 類的程式碼。可以在"https://github.com/protocolbuffers/protobuf/releases/".找到二進位制檔案。

根據作業系統選擇正確的二進位制檔案。我們將在 Windows 上安裝 proto 二進位制檔案,但 Linux 的步驟差別不大。

我們已經下載了https://github.com/protocolbuffers/protobuf/releases/download/v27.3/protoc-27.3-win64.zip

驗證 Proto 編譯器設定

安裝完成後,確保可以透過命令列訪問它:-

protoc --version

libprotoc 27.3

它確認 Protobuf 已正確安裝。現在讓我們繼續為 Java建立上面描述的問候應用程式。

專案結構

以下是我們將擁有的整體專案結構:-

Project Structure

Java 中的問候應用程式

現在我們已經安裝了protoc,我們可以使用protoc從 proto 檔案自動生成程式碼。不過,讓我們先建立一個 Java 專案。

以下是我們將用於 Java 專案的 Maven 配置。請注意,它還包含Protobuf-java所需的庫。

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.tutorials.point</groupId>
   <artifactId>protobuf-tutorial</artifactId>
   <version>1.0</version>
   <packaging>jar</packaging>

   <properties>
      <maven.compiler.source>21</maven.compiler.source>
      <maven.compiler.target>21</maven.compiler.target>
   </properties>

   <dependencies>
      <!-- https://mvnrepository.com/artifact/com.google.protobuf/protobuf-java -->
      <dependency>
         <groupId>com.google.protobuf</groupId>
         <artifactId>protobuf-java</artifactId>
         <version>4.27.3</version>
      </dependency>
   </dependencies>

   <build>
      <plugins>
         <plugin>
            <groupId>org.apache.maven.plugins</groupId>
            <artifactId>maven-shade-plugin</artifactId>
            <version>3.2.4</version>
            <configuration>
               <!--Put your configurations here-->
            </configuration>
            <executions>
               <execution>
                  <phase>package</phase>
                     <goals>
                     <goal>shade</goal>
                  </goals>
               </execution>
            </executions>
         </plugin>
      </plugins>
   </build>
</project>

我們所有的程式碼都將位於src/main/java下。

在專案結構完成後,讓我們為Greet類生成程式碼:-

生成 Java 類

protoc --java_out=. greeting.proto

執行命令後,您將在當前目錄內的com > tutorialspoint > greeting資料夾下看到一個自動生成的類。

  • Greeting.java

此檔案包含一個類Greeting和一個介面GreetOrBuilder,它們將幫助我們序列化和反序列化Greet物件。

使用生成的 Java 類

現在,讓我們編寫資料的寫入器,它將以使用者名稱問候語作為輸入:-

GreetWriter.java

package com.tutorialspoint.greeting;

import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.greeting.Greeting.Greet;

public class GreetWriter{
   public static void main(String[] args) throws IOException {
      Greet greeting = Greet.newBuilder()
         .setGreeting(args[0])
         .setUsername(args[1])
         .build();
		
      String filename = "greeting_protobuf_output";
      System.out.println("Saving greeting to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         greeting.writeTo(output);
      }
      System.out.println("Saved greeting with following data to disk: \n" + greeting);
   }
}

寫入器只需獲取 CLI 引數,建立Greet物件,將其序列化,然後將其轉儲到檔案中。

現在讓我們編寫一個將讀取檔案的讀取器:-

GreetReader.java

package com.tutorialspoint.greeting;

import java.io.FileInputStream;
import java.io.IOException;
import com.tutorialspoint.greeting.Greeting.Greet;

public class GreetReader{
   public static void main(String[] args) throws IOException {
      Greet.Builder greetBuilder = Greet.newBuilder();

      String filename = "greeting_protobuf_output";
      System.out.println("Reading from file " + filename);
	    
      try(FileInputStream input = new FileInputStream(filename)) {
         Greet greet = greetBuilder.mergeFrom(input).build();
         System.out.println("Greeting: " + greet.getGreeting() + "\n" + "Username: " + greet.getUsername());
      }
   }
}

讀取器只需從同一檔案讀取,將其反序列化,並列印有關問候語的資料。

編譯專案

現在我們已經設定了讀取器寫入器,讓我們編譯專案。

mvn clean install

序列化 Java 物件

現在,讓我們首先執行寫入器以將物件序列化到檔案系統中。

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.greeting.GreetWriter Hello John

Saving greeting to file: 
greeting_protobuf_output

Saved greeting with following data to disk:
greeting: Hello
username: John

反序列化已序列化的物件

然後,讓我們執行讀取器以從檔案系統中反序列化物件。

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.greeting.GreetReader

Reading from file greeting_protobuf_output
Greeting: Hello
Username: John

因此,正如我們看到的,寫入器序列化並儲存到檔案中的資料,被讀取器正確地反序列化並相應地打印出來。

Python 中的問候應用程式

現在讓我們將同一個示例編寫為 Python 專案:-

安裝 protocol buffers 庫

在繼續之前,我們需要安裝protobuf pip 包

pip install protobuf

我們所有的程式碼都將位於python目錄下。

Project Structure Python

從 proto 檔案生成 Python 類

在專案結構完成後,讓我們為 Greet 類生成程式碼:-

protoc  --python_out=. greeting.proto

執行此命令後,您將在當前目錄中看到一個自動生成的類greeting_pb2.py。此類將幫助我們序列化和反序列化Greet物件。

使用生成的 Python 類

現在,讓我們編寫資料的寫入器,它將以使用者名稱問候語作為輸入:-

greetWriter.py

import greeting_pb2

import sys

greet = greeting_pb2.Greet()
greet.username = sys.argv[1]
greet.greeting = sys.argv[2]

filename = "greeting_protobuf_output";
print("Saving to file: " + filename)

f = open(filename, "wb")
f.write(greet.SerializeToString())
f.close()
print("Saved following greeting to disk: \n" + str(greet))

寫入器只需獲取 CLI 引數,建立Greet物件,將其序列化,然後將其轉儲到檔案中。

現在讓我們建立一個將讀取檔案的讀取器:-

greetReader.py

import greeting_pb2

greet = greeting_pb2.Greet()

filename = "greeting_protobuf_output";
print("Reading from file: " + filename)

f = open(filename, "rb")
greet.ParseFromString(f.read())
f.close()

print("Read greeting from disk: \n" + str(greet))

讀取器只需從同一檔案讀取,將其反序列化,並列印有關問候語的資料。

序列化 Python 物件

現在,讓我們首先執行寫入器

py greetWriter.py Hola Jane

Saving to file: greeting_protobuf_output
Saved following greeting to disk:
greeting: "Hola"
username: "Jane"

反序列化 Python 物件

然後,讓我們執行讀取器

python greetReader.py

Reading from file: greeting_protobuf_output
Read greeting from disk:
greeting: "Hola"
username: "Jane"

因此,正如我們所看到的,寫入器序列化並儲存到檔案中的資料。接下來,讀取器正確地反序列化了相同的資料並相應地打印出來。

Protocol Buffers - 構造

概述

現在讓我們來看一下 Google Protocol Buffers 提供的一些基本資料結構和資料型別。我們將使用電影院的例子來了解這些資料結構。

請注意,雖然我們將使用 Java 程式碼來演示這個結構,但在 Python 程式碼中使用它們也同樣簡單且可行。

在接下來的幾章中,我們將逐一討論以下 Protocol Buffers 資料型別:

資料型別

  • message − “message” 是 Protocol Buffers 的一個非常基本的基礎構建塊。它在我們使用的語言(例如 Java、Python 等)中轉換為類。

  • string − “string” 資料型別在我們使用的語言(例如 Java、Python 等)中轉換為字串。

  • Numbers − 數字包括 Protocol Buffers 型別,如 int32、int64、float、double,它們是 Protobuf 的基本構建塊。它分別在我們在使用的語言(例如 Java、Python 等)中轉換為 int、long、float、double。

  • bool − “bool” 資料型別是 Protocol Buffers 的基本構建塊之一。它在我們使用的語言(例如 Java、Python 等)中轉換為布林值。

  • enum − “enum” 是 Protocol Buffers 的複合資料型別之一。它在我們使用的語言(例如 Java)中轉換為列舉。

  • repeated − “repeated” 用於建立陣列或列表,是 Protocol Buffers 的複合資料型別之一。Protocol Buffers 將其轉換為 Java 中的 java.util.list 介面。

  • map − “Map” 是 Protocol Buffers 的複合資料型別之一。Protocol Buffers 將其轉換為 Java 中的 java.util.Map 介面。

  • 巢狀類 − 我們可以使用用“message”建立的類在另一個“message”中,從而建立巢狀類。Protocol Buffers 將其轉換為巢狀的 Java 類。

Protocol Buffers - 訊息

概述

Protocol Buffers 的最基本構建塊是message屬性。它在我們使用的語言(例如 Java、Python 等)中轉換為

程式碼示例

以下是我們需要使用的語法,用於指示 Protocol Buffers 我們將建立給定類的例項:

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
}

我們將以上內容儲存在“theater.proto”中,並在我們探索其他資料結構時使用它。

解釋

這裡的“syntax”表示我們使用的是哪個版本的 Protocol Buffers。因此,我們使用的是最新的版本 3,並且該模式可以使用所有對版本 3 有效的語法。

syntax = "proto3";

這裡的包用於衝突解決,例如,如果我們有多個同名的類/訊息。

package tutorial;

此引數特定於 Java,即“.proto”檔案中的程式碼將自動生成的包。

option java_package = "com.tutorialspoint.greeting";

現在我們完成了先決條件,這裡最後一項是:

message Theater

這僅僅是將要建立/重新建立的物件的基礎類的類名。請注意,它在當前狀態下毫無用處,因為它沒有任何其他屬性。但隨著我們繼續,我們將新增更多屬性。

使用多個 message 屬性

單個 proto 檔案也可以有多個類/訊息。例如,如果我們想,我們也可以在同一個檔案中新增Visitors訊息/類。Protocol Buffers 將確保為其建立兩個獨立的類。例如:

syntax = "proto3";

package theater;

option java_package = "com.tutorialspoint.theater";

message Theater {
}
message Visitor {
}

從 proto 檔案建立 Java 類

要使用 Protocol Buffers,我們現在必須使用protoc二進位制檔案從此“.proto”檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=. proto_files\theater.proto

使用從 proto 檔案建立的 Java 類

就是這樣!以上命令應該在當前目錄中建立所需的檔案,現在我們可以在我們的 Java 程式碼中使用它們了:

Theater theater = Theater.newBuilder().build()
Visitor visitor = Visitor.newBuilder().build()

在這個階段,它沒有太大用處,因為我們還沒有向成員/類新增任何屬性。當我們在Protocol Buffers - string章節中檢視字串時,我們將執行此操作。

Protocol Buffers - 字串

概述

Protobuf 字串在我們在使用的語言(例如 Java、Python 等)中轉換為字串。繼續使用theater示例,以下是我們需要使用的語法,用於指示 Protobuf 我們將建立一個字串

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   string name = 1;
   string address = 2;
}

現在我們的類/訊息包含兩個字串屬性。它們每個都具有一個位置,這是 Protobuf 在序列化和反序列化時使用的。成員的每個屬性都需要具有唯一的位置屬性。

從 Proto 檔案建立 Java 類

要使用 Protobuf,我們現在必須使用protoc二進位制檔案從此“.proto”檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=. theater.proto

這將在當前目錄中的com > tutorialspoint > theater資料夾中建立一個 TheaterOuterClass.java 類。我們將在我們的應用程式中使用此類,類似於在Protocol Buffers - Basic App章節中所做的那樣。

使用從 Proto 檔案建立的 Java 類

首先讓我們建立一個writer來寫入theater資訊:

TheaterWriter.java

package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
      Theater theater = Theater.newBuilder()
         .setName("Silver Screener")
         .setAddress("212, Maple Street, LA, California")
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}	

接下來,我們將有一個reader來讀取theater資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.greeting.Greeting.Greet;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
      Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(theater);
      }
   }
}

編譯專案

現在我們已經設定了讀取器寫入器,讓我們編譯專案。

mvn clean install

序列化 Java 物件

現在,編譯後,讓我們先執行writer

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
name: "Silver Screener"
address: "212, Maple Street, LA, California"

反序列化已序列化的物件

現在,讓我們執行reader從同一個檔案讀取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
name: "Silver Screener"
address: "212, Maple Street, LA, California"

因此,正如我們所看到的,我們能夠透過將二進位制資料反序列化到Theater物件來讀取序列化的字串。在下一章Protocol Buffers - Numbers中,我們將瞭解數字

Protocol Buffers - 數字

概述

數字包括 protobuf 型別,如int32、int64、float、double,它們是 Protobuf 的基本構建塊。它分別在我們在使用的語言(例如 Java、Python 等)中轉換為int、long、float、double

繼續我們來自Protocol Buffers - String章節的theater示例,以下是我們需要使用的語法,用於指示 Protobuf 我們將建立數字

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   int32 total_capcity = 3;
   int64 mobile = 4;
   float base_ticket_price = 5;
}

現在我們的類/訊息包含數值屬性。它們每個都具有一個位置,這是 Protobuf 在序列化和反序列化時使用的。成員的每個屬性都需要分配一個唯一的數字。

從 Proto 檔案建立 Java 類

要使用 Protobuf,我們現在必須使用protoc二進位制檔案從此“.proto”檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=. theater.proto

這將在當前目錄中的com > tutorialspoint > theater資料夾中建立一個 TheaterOuterClass.java 類。我們將在我們的應用程式中使用此類,類似於在Protocol Buffers - Basic App章節中所做的那樣。

使用從 Proto 檔案建立的 Java 類

TheaterWriter.java

package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
      Theater theater = Theater.newBuilder()
         .setTotalCapcity(320)
         .setMobile(98234567189L)
         .setBaseTicketPrice(22.45f)
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下來,我們將有一個reader來讀取theater資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.greeting.Greeting.Greet;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
      Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(theater.getBaseTicketPrice());
         System.out.println(theater);
      }
   }
}

編譯專案

現在我們已經設定了讀取器寫入器,讓我們編譯專案。

mvn clean install

序列化 Java 物件

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar 
com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
total_capcity: 320
mobile: 98234567189
base_ticket_price: 22.45

反序列化已序列化的物件

現在,讓我們執行reader從同一個檔案讀取:

java -cp .\target\protobuf-tutorial-1.0.jar 
com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
22.45
total_capcity: 320
mobile: 98234567189
base_ticket_price: 22.45

因此,正如我們所看到的,我們能夠透過將二進位制資料反序列化到Theater物件來讀取序列化的int、floatlong。在下一章Protocol Buffers - bool中,我們將瞭解布林型別。

Protocol Buffers - 布林值

概述

bool資料型別是 Protobuf 的基本構建塊之一。它在我們使用的語言(例如JavaPython等)中轉換為布林值

繼續我們來自Protocol Buffers - String章節的theater示例,以下是我們需要使用的語法,用於指示 Protobuf 我們將建立bool

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   bool drive_in = 6;
}

現在我們的訊息類包含一個bool屬性。它還有一個位置,這是 Protobuf 在序列化和反序列化時使用的。成員的每個屬性都需要分配一個唯一的數字。

從 Proto 檔案建立 Java 類

要使用 Protobuf,我們現在必須使用protoc二進位制檔案從此“.proto”檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=. theater.proto

這將在當前目錄中的com > tutorialspoint > theater資料夾中建立一個 TheaterOuterClass.java 類。我們將在我們的應用程式中使用此類,類似於在Protocol Buffers - Basic App章節中所做的那樣。

使用從 Proto 檔案建立的 Java 類

TheaterWriter.java

package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;

import com.tutorialspoint.theater.TheaterOuterClass.Theater;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
      Theater theater = Theater.newBuilder()
         .setDriveIn(true)
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下來,我們將有一個reader來讀取theater資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.IOException;

import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
      Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(theater.getDriveIn());
         System.out.println(theater);
      }
   }
}

編譯專案

現在我們已經設定了讀取器寫入器,讓我們編譯專案。

mvn clean install

序列化 Java 物件

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
drive_in: true

反序列化已序列化的物件

現在,讓我們執行reader從同一個檔案讀取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
drive_in: true

因此,正如我們所看到的,我們能夠透過將二進位制資料反序列化到Theater物件來讀取序列化的bool。在下一章Protocol Buffers - enum中,我們將瞭解列舉,一種複合型別。

Protocol Buffers - 列舉

概述

enum資料型別是 Protobuf 的複合資料型別之一。它在我們使用的語言(例如Java等)中轉換為列舉

繼續我們來自Protocol Buffers - String章節的theater示例,以下是我們需要使用的語法,用於指示 Protobuf 我們將建立一個enum

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   enum PAYMENT_SYSTEM{
      CASH = 0;
      CREDIT_CARD = 1;
      DEBIT_CARD = 2;
      APP = 3;  
   }
   PAYMENT_SYSTEM payment = 7;
}

現在我們的訊息類包含一個enum屬性。它還有一個位置,這是 Protobuf 在序列化和反序列化時使用的。成員的每個屬性都需要分配一個唯一的數字。

我們定義了enum並在下面將其與“payment”屬性一起用作資料型別。請注意,儘管我們在訊息類內部定義了列舉,但它也可以駐留在其外部。

從 Proto 檔案建立 Java 類

要使用 Protobuf,我們現在必須使用protoc二進位制檔案從此“.proto”檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=. theater.proto

這將在當前目錄中的com > tutorialspoint > theater資料夾中建立一個 TheaterOuterClass.java 類。我們將在我們的應用程式中使用此類,類似於在Protocol Buffers - Basic App章節中所做的那樣。

使用從 Proto 檔案建立的 Java 類

TheaterWriter.java

package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.PAYMENT_SYSTEM;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
      Theater theater = Theater.newBuilder()
         .setPayment(PAYMENT_SYSTEM.CREDIT_CARD)
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
	    
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下來,我們將有一個reader來讀取theater資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.greeting.Greeting.Greet;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
      Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(theater.getPayment());
         System.out.println(theater);
      }
   }
}

編譯專案

現在我們已經設定了讀取器寫入器,讓我們編譯專案。

mvn clean install

序列化 Java 物件

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
payment: CREDIT_CARD

反序列化已序列化的物件

現在,讓我們執行reader從同一個檔案讀取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
CREDIT_CARD
payment: CREDIT_CARD

因此,正如我們所看到的,我們能夠透過將二進位制資料反序列化到Theater物件來讀取序列化的enum。在下一章Protocol Buffers - repeated中,我們將瞭解repeated,一種複合型別。

Protocol Buffers - 重複

概述

repeated資料型別是 Protobuf 的複合資料型別之一。它在Java中轉換為java.util.List介面。

繼續我們來自Protocol Buffers - String章節的theater示例,以下是我們需要使用的語法,用於指示 Protobuf 我們將建立一個repeated

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   repeated string snacks = 8;
}

現在我們的訊息類包含一個repeated屬性。請注意,儘管我們有一個字串repeated,但我們也可以有數字、布林值、自定義資料型別列表。它還有一個位置,這是 Protobuf 在序列化和反序列化時使用的。成員的每個屬性都需要分配一個唯一的數字。

從 Proto 檔案建立 Java 類

要使用 Protobuf,我們現在必須使用protoc二進位制檔案從此“.proto”檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=. theater.proto

這將在當前目錄中的com > tutorialspoint > theater資料夾中建立一個 TheaterOuterClass.java 類。我們將在我們的應用程式中使用此類,類似於在Protocol Buffers - Basic App章節中所做的那樣。

使用從 Proto 檔案建立的 Java 類

TheaterWriter.java

package com.tutorialspoint.theater;

import java.util.List;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
      List<String> snacks = new ArrayList<>();
      snacks.add("Popcorn");
      snacks.add("Coke");
      snacks.add("Chips");
      snacks.add("Soda");
	    
      Theater theater = Theater.newBuilder()
         .addAllSnacks(snacks)
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下來,我們將有一個reader來讀取theater資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.greeting.Greeting.Greet;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
      Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(theater);
      }
   }
}

編譯專案

現在我們已經設定了讀取器寫入器,讓我們編譯專案。

mvn clean install

序列化 Java 物件

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
snacks: "Popcorn"
snacks: "Coke"
snacks: "Chips"
snacks: "Soda"

反序列化已序列化的物件

現在,讓我們執行reader從同一個檔案讀取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
snacks: "Popcorn"
snacks: "Coke"
snacks: "Chips"
snacks: "Soda"

因此,正如我們所看到的,我們能夠透過將二進位制資料反序列化到Theater物件來讀取序列化的repeated。在下一章Protocol Buffers - map中,我們將瞭解map,一種複合型別。

Protocol Buffers - 對映

概述

map資料型別是 Protobuf 的複合資料型別之一。它在Java中轉換為java.util.Map介面。

繼續我們來自Protocol Buffers - String章節的theater示例,以下是我們需要使用的語法,用於指示 Protobuf 我們將建立一個repeated

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   map<string, int32> movieTicketPrice = 9;
}

現在我們的訊息類包含一個電影及其票價的map。請注意,儘管我們有“string -> int”map,但我們也可以有數字、布林值和自定義資料型別。但是,請注意,我們不能有巢狀的map。它還有一個位置,這是 Protobuf 在序列化和反序列化時使用的。成員的每個屬性都需要分配一個唯一的數字。

從 Proto 檔案建立 Java 類

要使用 Protobuf,我們現在必須使用protoc二進位制檔案從此“.proto”檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=. theater.proto

這將在當前目錄中的com > tutorialspoint > theater資料夾中建立一個 TheaterOuterClass.java 類。我們將在我們的應用程式中使用此類,類似於在Protocol Buffers - Basic App章節中所做的那樣。

使用從 Proto 檔案建立的 Java 類

TheaterWriter.java

package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

import com.tutorialspoint.theater.TheaterOuterClass.Theater;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
      Map<String, Integer> ticketPrice = new HashMap<>();
	    
      ticketPrice.put("Avengers Endgame", 700);
      ticketPrice.put("Captain America", 200);
      ticketPrice.put("Wonder Woman 1984", 400);
	    
      Theater theater = Theater.newBuilder()
         .putAllMovieTicketPrice(ticketPrice)
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下來,我們將有一個reader來讀取theater資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.IOException;

import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
      Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(theater);
      }
   }
}

編譯專案

現在我們已經設定了讀取器寫入器,讓我們編譯專案。

mvn clean install

序列化 Java 物件

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
movieTicketPrice {
   key: "Avengers Endgame"
   value: 700
}
movieTicketPrice {
   key: "Captain America"
   value: 200
}
movieTicketPrice {
   key: "Wonder Woman 1984"
   value: 400
}

反序列化已序列化的物件

現在,讓我們執行reader從同一個檔案讀取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
movieTicketPrice {
   key: "Avengers Endgame"
   value: 700
}
movieTicketPrice {
   key: "Captain America"
   value: 200
}
movieTicketPrice {
   key: "Wonder Woman 1984"
   value: 400
}

因此,正如我們所看到的,我們能夠透過將二進位制資料反序列化到Theater物件來讀取序列化的map。在下一章Protocol Buffers - Nested Class中,我們將瞭解巢狀類。

Protocol Buffers - 巢狀類

概述

在這裡,我們將看到如何建立巢狀類。Protobuf 將其轉換為巢狀的Java類。

繼續我們來自Protocol Buffers - String章節的theater示例,以下是我們需要使用的語法,用於指示 Protobuf 我們將建立一個repeated

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   TheaterOwner owner = 10;
}
message TheaterOwner{
   string name = 1;
   string address = 2;
}

現在我們的訊息類包含一個巢狀類,即影院所有者資訊。

從 Proto 檔案建立 Java 類

要使用 Protobuf,我們現在必須使用protoc二進位制檔案從此“.proto”檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=. theater.proto

這將在當前目錄中的com > tutorialspoint > theater資料夾中建立一個 TheaterOuterClass.java 類。我們將在我們的應用程式中使用此類,類似於在Protocol Buffers - Basic App章節中所做的那樣。

使用從 Proto 檔案建立的 Java 類

TheaterWriter.java

package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;

import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.TheaterOwner;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
      TheaterOwner owner = TheaterOwner.newBuilder()
         .setName("Anthony Gonsalves")
         .setAddress("513, St Paul Street, West Coast, California")
         .build();
	    
      Theater theater = Theater.newBuilder()
         .setOwner(owner)
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下來,我們將有一個reader來讀取theater資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.IOException;

import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
      Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(theater);
      }
   }
}

編譯專案

現在我們已經設定了讀取器寫入器,讓我們編譯專案。

mvn clean install

序列化 Java 物件

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
owner {
   name: "Anthony Gonsalves"
   address: "513, St Paul Street, West Coast, California"
}

反序列化已序列化的物件

現在,讓我們執行reader從同一個檔案讀取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
owner {
   name: "Anthony Gonsalves"
   address: "513, St Paul Street, West Coast, California"
}

因此,正如我們所看到的,我們能夠透過將二進位制資料反序列化到Theater物件來讀取序列化的巢狀類

Protocol Buffers - 可選性和預設值

概述

雖然我們已經瞭解了各種資料型別以及如何使用它們,但如果在序列化時不指定值會發生什麼?“proto2”版本支援“required”“optional”標籤,這有助於確定如果所需的解析邏輯不可用,序列化/反序列化是否應該失敗。但“required”標籤在“proto3”版本中被移除。失敗部分需要由相應的程式碼處理。現在每個屬性都是可選的,並且都有預設值。因此,從“proto3”版本開始,“optional”的使用就變得多餘了。

Protocol Buffers根據下表支援其資料型別的預設值:

資料型別 預設值
Int32 / Int64 0
Float/double 0.0
String 空字串
Boolean False
Enum 第一個列舉項,即“index=0”的項
重複型別 空列表
Map 空Map
巢狀類 null

因此,如果未為這些資料型別指定資料,則它們將採用上述預設值。現在,讓我們繼續使用我們的theater示例來演示其工作原理。

在此示例中,我們將讓所有欄位都使用預設值。唯一指定的欄位將是劇院的名稱。

繼續我們從Protocol Buffers - String 章節中的theater示例,以下是我們需要使用的語法來指示Protobuf我們將建立不同的資料型別:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   string name = 1;
   string address = 2;
  
   int32 total_capcity = 3;
   int64 mobile = 4;
   float base_ticket_price = 5;
  
   bool drive_in = 6;
  
   enum PAYMENT_SYSTEM {
      CASH = 0;
      CREDIT_CARD = 1;
      DEBIT_CARD = 2;
      APP = 3;
   }
 
   PAYMENT_SYSTEM payment = 7;
   repeated string snacks = 8;
   map<string, int32> movieTicketPrice = 9;
   TheaterOwner owner = 10;
}
message TheaterOwner{
   string name = 1;
   string address = 2;
}

現在我們的message類包含多個屬性。

從 Proto 檔案建立 Java 類

要使用 Protobuf,我們現在必須使用protoc二進位制檔案從此“.proto”檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=. theater.proto

這將在當前目錄中的com > tutorialspoint > theater資料夾中建立一個 TheaterOuterClass.java 類。我們將在我們的應用程式中使用此類,類似於在Protocol Buffers - Basic App章節中所做的那樣。

使用從 Proto 檔案建立的 Java 類

TheaterWriter.java

package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;

import com.tutorialspoint.theater.TheaterOuterClass.Theater;

public class TheaterWriter {
   public static void main(String[] args) throws IOException {
      Theater theater = Theater.newBuilder()
         .setName("SilverScreen")
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下來,我們將有一個reader來讀取theater資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.IOException;

import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
      Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(
            "Name:" + theater.getName() + "\n" +
            "Address:" + theater.getAddress() + "\n" +
            "Drive_In:" + theater.getDriveIn() + "\n" +
            "Total Capacity:" + theater.getTotalCapcity() + "\n" +
            "Base Ticket Prices: " + theater.getBaseTicketPrice() + "\n" +
            "Owner: " + theater.getOwner() + "\n" +
            "Snacks: " + theater.getSnacksList() + "\n" +
            "Payment: " + theater.getPayment()
         );
            
         //Map<FieldDescriptor, Object> f = theater.getAllFields();
         System.out.println("List of fields explicitly specified: " + theater.getAllFields());
      }
   }
}

編譯專案

現在我們已經設定了讀取器寫入器,讓我們編譯專案。

mvn clean install

序列化 Java 物件

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
name: "SilverScreen"

反序列化已序列化的物件

現在,讓我們執行reader從同一個檔案讀取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
Name:SilverScreen
Address:
Drive_In:false
Total Capacity:0
Base Ticket Prices: 0.0
Owner:
Snacks: []
Payment: CASH
List of fields explicitly specified: {theater.Theater.name=SilverScreen}

因此,正如我們所看到的,除了我們已明確指定為最底層行的name之外,所有值都相應地使用了預設值。

Protocol Buffers - 語言獨立性

概述

到目前為止,我們一直在使用Java來序列化和反序列化電影院資料。但是,Google Protocol Buffers提供的關鍵功能之一是“語言獨立性”。在本節中,我們將瞭解如何使用Java進行序列化,並使用Python進行反序列化。

繼續我們從Protocol Buffers - String 章節中的theater示例,以下是我們需要使用的語法來指示Protobuf我們將建立不同的資料型別:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   string name = 1;
   string address = 2;
  
   int32 total_capcity = 3;
   int64 mobile = 4;
   float base_ticket_price = 5;
  
   bool drive_in = 6;
   
   enum PAYMENT_SYSTEM { 
      CASH = 0;
      CREDIT_CARD = 1;
      DEBIT_CARD = 2;
      APP = 3;
   }
   PAYMENT_SYSTEM payment = 7;
   repeated string snacks = 8;
   map<string, int32> movieTicketPrice = 9;
   TheaterOwner owner = 10;
}
message TheaterOwner{
   string name = 1;
   string address = 2;
}

使用Java進行序列化

要使用 Protobuf,我們現在必須使用protoc二進位制檔案從此“.proto”檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=. theater.proto

這將在當前目錄中的com > tutorialspoint > theater資料夾中建立一個 TheaterOuterClass.java 類。我們將在我們的應用程式中使用此類,類似於在Protocol Buffers - Basic App章節中所做的那樣。

使用從 Proto 檔案建立的 Java 類

首先,我們將建立一個寫入器來寫入劇院資訊:

TheaterWriter.java

package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.TheaterOwner;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.PAYMENT_SYSTEM;

public class TheaterWriter {
   public static void main(String[] args) throws IOException {
      TheaterOwner owner = TheaterOwner.newBuilder()
         .setName("Anthony Gonsalves")
         .setAddress("513, St Paul Street, West Coast, California")
         .build();
	    
      List<String> snacks = new ArrayList<>();
      snacks.add("Popcorn");
      snacks.add("Coke");
      snacks.add("Chips");
      snacks.add("Soda");
	        
      Map<String, Integer> ticketPrice = new HashMap<>();
      ticketPrice.put("Avengers Endgame", 700);
      ticketPrice.put("Captain America", 200);
      ticketPrice.put("Wonder Woman 1984", 400);
	   
      Theater theater = Theater.newBuilder()
         .setName("Silver Screener")
         .setAddress("212, Maple Street, LA, California")
         .setDriveIn(true)
         .setTotalCapcity(320)
         .setMobile(98234567189L)
         .setBaseTicketPrice(22.45f)
         .setPayment(PAYMENT_SYSTEM.CREDIT_CARD)
         .putAllMovieTicketPrice(ticketPrice)
         .addAllSnacks(snacks)
         .setOwner(owner)
         .build();
		
      String filename = "E:/theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

讓我們編譯專案。

mvn clean install

序列化 Java 物件

現在,編譯後,讓我們執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: E:/theater_protobuf_output
Saved theater information with following data to disk:
name: "Silver Screener"
address: "212, Maple Street, LA, California"
total_capcity: 320
mobile: 98234567189
base_ticket_price: 22.45
drive_in: true
payment: CREDIT_CARD
snacks: "Popcorn"
snacks: "Coke"
snacks: "Chips"
snacks: "Soda"
movieTicketPrice {
   key: "Avengers Endgame"
   value: 700
}
movieTicketPrice {
   key: "Captain America"
   value: 200
}
movieTicketPrice {
   key: "Wonder Woman 1984"
   value: 400
}
owner {
   name: "Anthony Gonsalves"
   address: "513, St Paul Street, West Coast, California"
}

使用Python反序列化序列化物件

從 proto 檔案生成 Python 類

讓我們為Theater類生成Python程式碼:

protoc  --python_out=. theater.proto

執行此命令後,您將在當前目錄中注意到一個自動生成的類theater_pb2.py。此類將幫助我們反序列化Theater物件。

使用生成的 Python 類

現在,讓我們編寫資料的讀取器,它將使用Java讀取包含序列化物件的檔案:

theaterReader.py

import theater_pb2

filename = "E:/theater_protobuf_output";
print("Reading from file: " + filename)

theater = theater_pb2.Theater()

f = open(filename, "rb")
theater.ParseFromString(f.read())
f.close()

print("Read theater from disk: \n" + str(theater))

然後,讓我們執行讀取器

python theaterReader.py

Reading from file: E:/greeting_protobuf_output
Read theater from disk:
name: "Silver Screener"
address: "212, Maple Street, LA, California"
total_capcity: 320
mobile: 98234567189
base_ticket_price: 22.45
drive_in: true
payment: CREDIT_CARD
snacks: "Popcorn"
snacks: "Coke"
snacks: "Chips"
snacks: "Soda"
movieTicketPrice {
  key: "Wonder Woman 1984"
  value: 400
}
movieTicketPrice {
  key: "Captain America"
  value: 200
}
movieTicketPrice {
  key: "Avengers Endgame"
  value: 700
}
owner {
  name: "Anthony Gonsalves"
  address: "513, St Paul Street, West Coast, California"
}

因此,正如我們所看到的,Java客戶端寫入的所有值都被正確地反序列化並由我們的Python客戶端讀取,這有效地意味著Protobuf是語言獨立的。

Protocol Buffers - 複合資料型別

概述

還有兩種複合資料型別可能對複雜的用例有用。它們是“OneOf”“Any”。在本節中,我們將瞭解如何使用Protobuf的這兩種資料型別。

OneOf

我們將一些引數傳遞給此OneOf資料型別,Protobuf確保其中只有一個被設定。如果我們設定了其中一個並嘗試設定另一個,則第一個屬性將被重置。讓我們透過一個示例來了解這一點。

繼續我們從Protocol Buffers - String 章節中的theater示例,假設我們有一個用於獲取可用員工數量的API。從該API返回的值隨後設定為以下檔案中的“count”標籤。但是,如果該API出錯,我們實際上無法“count”,而是附加錯誤日誌。

理想情況下,我們將始終設定其中之一,即呼叫成功並獲得計數,或者計數計算失敗並獲得錯誤訊息。

以下是我們需要使用的語法來指示Protobuf我們將建立一個OneOf屬性:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater { 
   oneof availableEmployees {
      int32 count = 4;
      string errorLog = 5;
   }
}

使用Java進行序列化

要使用 Protobuf,我們現在必須使用protoc二進位制檔案從此“.proto”檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=. theater.proto

這將在當前目錄中的com > tutorialspoint > theater資料夾中建立一個 TheaterOuterClass.java 類。我們將在我們的應用程式中使用此類,類似於在Protocol Buffers - Basic App章節中所做的那樣。

使用從 Proto 檔案建立的 Java 類

首先,我們將建立一個寫入器來寫入劇院資訊:

TheaterWriter.java

package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;

import com.tutorialspoint.theater.TheaterOuterClass.Theater;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
    	   
      Theater theater = Theater.newBuilder()
         .setCount(3)
         .setErrorLog("No employee found")
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下來,我們將有一個reader來讀取theater資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.IOException;

import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
      Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(theater);
      }
   }
}

編譯專案

現在我們已經設定了讀取器寫入器,讓我們編譯專案。

mvn clean install

序列化 Java 物件

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
errorLog: "No employee found"

反序列化已序列化的物件

現在,讓我們執行reader從同一個檔案讀取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
errorLog: "No employee found"

因此,正如我們所看到的,我們只能看到錯誤日誌被設定為後面的內容。

Any

下一個可用於複雜用例的資料型別是Any。我們可以將任何型別/訊息/類傳遞給此資料型別,Protobuf不會報錯。讓我們透過一個示例來了解這一點。

繼續劇院示例,假設我們想要跟蹤劇院內部的人員。其中一些可能是員工,另一些可能是觀眾。但最終它們都是人,因此我們將把它們放在一個包含這兩種型別的列表中。

現在我們的class/message包含一個Any屬性'peopleInside'列表以及ViewerEmployee類,即劇院內人員的資訊。讓我們看看它是如何工作的。

以下是我們需要使用的語法來指示Protobuf我們將建立一個Any屬性:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

import "google/protobuf/any.proto";

message Theater {
   string name = 1;
   string address = 2;
  
   repeated google.protobuf.Any peopleInside = 3;
}
message Employee{
   string name = 1;
   string address = 2;
}
message Viewer{
   string name = 1;
   int32 age = 2;
   string sex = 3;
}

使用Java進行序列化

要使用 Protobuf,我們現在必須使用protoc二進位制檔案從此“.proto”檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=. theater.proto

這將在當前目錄中的com > tutorialspoint > theater資料夾中建立一個 TheaterOuterClass.java 類。我們將在我們的應用程式中使用此類,類似於在Protocol Buffers - Basic App章節中所做的那樣。

使用從 Proto 檔案建立的 Java 類

首先,我們將建立一個寫入器來寫入劇院資訊:

TheaterWriter.java

package com.tutorialspoint.theater;

import java.util.List;
import java.io.FileOutputStream;
import java.io.IOException;
import java.util.ArrayList;

import com.google.protobuf.Any;
import com.tutorialspoint.theater.TheaterOuterClass.Employee;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Viewer;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
	    
   List<Any> people = new ArrayList<>();
   people.add(Any.pack(Employee.newBuilder().setName("John").build()));
   people.add(Any.pack(Viewer.newBuilder().setName("Jane").setAge(30).build()));
   people.add(Any.pack(Employee.newBuilder().setName("Simon").build()));
   people.add(Any.pack(Viewer.newBuilder().setName("Janice").setAge(25).build()));
	    
   Theater theater = Theater.newBuilder()
      .setName("SilverScreen")
      .addAllPeopleInside(people)
      .build();
		
      String filename = "theater_protobuf_output_silver";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
	    
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下來,我們將有一個reader來讀取theater資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.IOException;

import com.google.protobuf.Any;
import com.tutorialspoint.theater.TheaterOuterClass.Employee;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;
import com.tutorialspoint.theater.TheaterOuterClass.Viewer;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
	   Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output_silver";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
            
         System.out.println("Name:" + theater.getName() + "\n");
            
         for (Any anyPeople : theater.getPeopleInsideList()) {
            if(anyPeople.is(Employee.class)) {
               Employee employee = anyPeople.unpack(Employee.class);
               System.out.println("Employee:" + employee + "\n");
            }
                
            if(anyPeople.is(Viewer.class)) {
               Viewer viewer = anyPeople.unpack(Viewer.class);
               System.out.println("Viewer:" + viewer + "\n");
            }
         }
      }
   }
}	

編譯專案

現在我們已經設定了讀取器寫入器,讓我們編譯專案。

mvn clean install

序列化 Java 物件

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
name: "SilverScreen"
peopleInside {
   type_url: "type.googleapis.com/theater.Employee"
   value: "\n\004John"
}
peopleInside {
   type_url: "type.googleapis.com/theater.Viewer"
   value: "\n\004Jane\020\036"
}
peopleInside {
   type_url: "type.googleapis.com/theater.Employee"
   value: "\n\005Simon"
}
peopleInside {
   type_url: "type.googleapis.com/theater.Viewer"
   value: "\n\006Janice\020\031"
}

注意 - 有兩點需要注意:

  • 對於Any,Protobuf將任何標籤內的內容打包/序列化為位元組,然後將其儲存為'value'。基本上,這允許我們使用此'Any'標籤傳送任何訊息型別。

  • 我們還看到了"type.googleapis.com/theater.Viewer""type.googleapis.com/theater.Employee"。Protobuf使用它來儲存物件型別以及資料,因為Any資料型別中的資料型別可能會有所不同。

反序列化已序列化的物件

現在讓我們執行reader以從同一檔案讀取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output_silver
Name:SilverScreen

Employee:name: "John"

Viewer:name: "Jane"
age: 30

Employee:name: "Simon"

Viewer:name: "Janice"
age: 25

因此,正如我們所看到的,即使我們的reader程式碼位於同一個陣列中,也能夠成功地區分EmployeeViewer

Protocol Buffers - 命令列使用

概述

Protobuf序列化資料並將其儲存在二進位制格式中。如果我們只是處理字串,這可能不是問題,因為最終Protobuf使用UTF-8。因此,如果使用支援UTF8的讀取器,它儲存的任何文字都將是人類可讀的。但是,int32、Boolean、list、maps等使用特定的技術進行編碼以減少空間消耗。

這就是為什麼有時透過簡單的命令列實用程式編碼/解碼訊息對於測試目的很有用。讓我們看看它是如何工作的:

假設我們使用以下簡單的"greeting_cli.proto"

syntax = "proto3";
package tutorial;
option java_package = "com.tutorialspoint.greeting";

message Greet {
   string greeting = 1;
   string username = 2;
   int32 age = 3;
}  

並且我們在cli_greeting_message中建立一條訊息:

greeting: "Yo"
username : "John"
age : 50

現在,讓我們使用Protobuf CLI工具對該訊息進行編碼:

cat  .\cli_greeting_msg.proto |  protoc  --encode=tutorial.Greet .\greeting_cli.proto > encoded_greeting

如果我們檢視此檔案內部的內容或cat此檔案:

cat .\encoded_greeting

☻Yo↕♦John↑2

除了"Yo""John"之外,您還會注意到一些奇怪的字元。這是因為這些編碼可能不是有效的Unicode/UTF-8編碼。UTF-8通常是在大多數地方使用的。在Protobuf的情況下,它用於string,但ints、maps、Boolean、list有單獨的格式。此外,此檔案還包含資料的元資料。

這就是為什麼我們需要解碼器/反序列化器來讀取這些資料。讓我們使用它。

cat .\encoded_greeting | protoc --decode=tutorial.Greet .\greeting_cli.proto

greeting: "Yo"
username : "John"
age : 50

因此,正如我們所看到的,我們能夠獲取序列化後並在檔案中看起來很奇怪的資料。

Protocol Buffers - 更新定義規則

概述

假設您已經提出了將在生產環境中使用的proto檔案的定義。將來肯定會有需要更改此定義的時候。在這種情況下,必須使我們進行的更改符合某些規則,以便更改向後相容。讓我們透過一些注意事項來了解這一點。

writer中新增一個新欄位,而reader保留舊版本的程式碼。

假設您決定新增一個新欄位。理想情況下,要新增新欄位,我們將不得不同時更新writerreader。但是,在大規模部署中,這是不可能的。在某些情況下,writer已更新,但reader尚未更新新欄位。這就是上述情況發生的地方。讓我們看看它是如何工作的。

繼續我們的theater示例,假設我們的proto檔案中只有一個標籤,即'name'。以下是我們需要使用的語法來指示Protobuf:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   string name = 1;
}

要使用Protobuf,我們現在將不得不使用protoc二進位制檔案從這個".proto"檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=. theater.proto

上述命令應該建立所需的檔案,現在我們可以在Java程式碼中使用它。首先,我們將建立一個writer來寫入theater資訊:

TheaterWriter.java

package com.tutorialspoint.theater;
package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
      Theater theater = Theater.newBuilder()
         .setName("Silver Screener")
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
	    
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下來,我們將有一個reader來讀取劇院資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.google.protobuf.ProtocolStringList;
import com.tutorialspoint.greeting.Greeting.Greet;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
	    
      Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(theater);
         System.out.println("Unknwon fields: " + theater.getUnknownFields());
      }
   }
}		

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
name: "Silver Screener"

現在讓我們執行reader以從同一檔案讀取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
name: "Silver Screener"

未知欄位

我們根據Protobuf定義簡單地寫入了一個字串,並且reader能夠讀取該字串。我們還看到讀取器沒有意識到任何未知欄位。

但是現在,假設我們想在Protobuf定義中新增一個新的字串'address'。現在,它將如下所示:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   string name = 1;
   string address = 2;
}

我們還將更新我們的writer並新增一個address欄位:

Theater theater = Theater.newBuilder()
   .setName("Silver Screener")
   .setAddress("212, Maple Street, LA, California")
   .build();

在編譯之前,將上一次編譯生成的JAR重新命名為protobuf-tutorial-old-1.0.jar。然後編譯。

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
name: "Silver Screener"
address: "212, Maple Street, LA, California"

現在讓我們執行reader以從同一檔案但從舊JAR中讀取:

java -cp .\target\protobuf-tutorial-old-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
Reading from file theater_protobuf_output
name: "Silver Screener"
2: "212, Maple Street, LA, California"

Unknown fields: 2: "212, Maple Street, LA, California"

從輸出的最後一行可以看出,舊的reader不知道新writer新增的address欄位。它只是展示了“新writer - 舊reader”組合的功能。

刪除欄位

假設您決定刪除現有欄位。理想情況下,要使已刪除欄位立即生效,我們將不得不同時更新writerreader。但是,在大規模部署中,這是不可能的。在某些情況下,writer已更新,但reader尚未更新。在這種情況下,reader仍將嘗試讀取已刪除的欄位。讓我們看看它是如何工作的。

繼續我們的theater示例,假設我們的proto檔案中只有兩個標籤。以下是我們需要使用的語法來指示Protobuf:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   string name = 1;
   string address = 2;
}

要使用Protobuf,我們現在將不得不使用protoc二進位制檔案從這個".proto"檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=java/src/main/java proto_files\theater.proto

上述命令應該建立所需的檔案,現在我們可以在Java程式碼中使用它。首先,我們將建立一個writer來寫入theater資訊:

TheaterWriter

package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
      Theater theater = Theater.newBuilder()
         .setName("Silver Screener")
         .setAddress("212, Maple Street, LA, California")
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
	    
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下來,我們將有一個reader來讀取theater資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.google.protobuf.ProtocolStringList;
import com.tutorialspoint.greeting.Greeting.Greet;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
      Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(theater);
         System.out.println("Unknwon fields: " + theater.getUnknownFields());
      }
   }
}		

現在,編譯後,讓我們首先執行writer:

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
name: "Silver Screener"
address: "212, Maple Street, LA, California"

現在讓我們執行reader以從同一檔案讀取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
name: "Silver Screener"
address: "212, Maple Street, LA, California"

所以,這裡沒有什麼新東西,我們只是根據Protobuf定義簡單地寫了一個string,並且reader能夠讀取string

但是現在,假設我們想從Protobuf定義中刪除字串'address'。因此,定義將如下所示:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   string name = 1;
}

我們還將更新我們的writer,如下所示:

Theater theater = Theater.newBuilder()
   .setName("Silver Screener")
   .build();

在編譯之前,將上一次編譯生成的JAR重新命名為protobuf-tutorial-old-1.0.jar。然後編譯。

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
name: "Silver Screener"

現在讓我們執行reader以從同一檔案但從舊JAR中讀取:

java -cp .\target\protobuf-tutorial-old-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
Reading from file theater_protobuf_output
name: "Silver Screener"
address:

從輸出的最後一行可以看出,舊的reader預設為“address”的值。它展示了“新writer - 舊reader”組合的功能。

避免重複使用欄位的序列號

在某些情況下,我們可能會錯誤地更新欄位的“序列號”。這可能存在問題,因為序列號對於Protobuf理解和反序列化資料至關重要。一些舊的reader可能依賴此序列號來反序列化資料。因此,建議您:

  • 不要更改欄位的序列號

  • 不要重複使用已刪除欄位的序列號。

讓我們透過交換欄位標籤來了解這一點。

繼續使用劇院示例,假設我們只有兩個標籤在我們的proto檔案中。以下是我們需要用來指示 Protobuf 的語法:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   string name = 1;
   string address = 2;
}

要使用Protobuf,我們現在將不得不使用protoc二進位制檔案從這個".proto"檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=java/src/main/java proto_files\theater.proto

上述命令應該建立所需的檔案,現在我們可以在Java程式碼中使用它。首先,我們將建立一個writer來寫入theater資訊:

TheaterWriter.java

package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
      Theater theater = Theater.newBuilder()
         .setName("Silver Screener")
         .setAddress("212, Maple Street, LA, California")
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

接下來,我們將有一個reader來讀取theater資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.google.protobuf.ProtocolStringList;
import com.tutorialspoint.greeting.Greeting.Greet;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
      Builder theaterBuilder = Theater.newBuilder();
      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(theater);
         System.out.println("Unknwon fields: " + theater.getUnknownFields());
      }
   }
}

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
name: "Silver Screener"
address: "212, Maple Street, LA, California"

接下來,讓我們執行讀取器從同一個檔案讀取:

java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
name: "Silver Screener"
address: "212, Maple Street, LA, California"

這裡,我們只是根據我們的 Protobuf 定義寫了簡單的字串,並且讀取器能夠讀取字串。但是現在,讓我們在 Protobuf 定義中交換序列號,使其如下所示:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   string name = 2;
   string address = 1;
}		

在編譯之前,將 JAR 從之前的編譯重新命名為protobuf-tutorial-old-1.0.jar。然後編譯。

現在,編譯後,讓我們首先執行writer:

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
address: "212, Maple Street, LA, California"
name: "Silver Screener"

現在讓我們執行reader以從同一檔案但從舊JAR中讀取:

java -cp .\target\protobuf-tutorial-old-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
name: "212, Maple Street, LA, California"
address: "Silver Screener"

從輸出中可以看到,舊的讀取器交換了地址名稱。這表明更新序列號以及結合“新的寫入器-舊的讀取器”不會按預期工作。

更重要的是,這裡我們有兩個字串,這就是我們能夠看到資料的原因。如果我們使用了不同的資料型別,例如int32、布林值、對映等,Protobuf 會放棄並將此視為未知欄位。

因此,務必不要更改欄位的序列號或重複使用已刪除欄位的序列號。

更改欄位型別

在某些情況下,我們需要更新屬性/欄位的型別。Protobuf 對此有一些相容性規則。並非所有型別都可以轉換為其他型別。需要注意的一些基本型別:

  • 如果位元組是 UTF-8,則字串位元組是相容的。這是因為 Protobuf 始終將字串編碼/解碼為 UTF-8。

  • 就值而言,列舉int32int64相容,但是客戶端可能無法按預期反序列化它。

  • int32、int64無符號也)以及布林值是相容的,因此可以互換。類似於語言中的強制轉換工作方式,可能會截斷多餘的字元。

但是更改型別時需要非常小心。讓我們透過將int64轉換為int32的錯誤示例來檢視其操作。

繼續使用劇院示例,假設我們只有兩個標籤在我們的proto檔案中。以下是我們需要用來指示 Protobuf 的語法:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";
message Theater {
   string name = 1;
   int64 total_capacity = 2;
}

要使用Protobuf,我們現在將不得不使用protoc二進位制檔案從這個".proto"檔案建立所需的類。讓我們看看如何做到這一點:

protoc  --java_out=java/src/main/java proto_files\theater.proto

上述命令應該建立所需的檔案,現在我們可以在Java程式碼中使用它。首先,我們將建立一個writer來寫入theater資訊:

TheaterWriter.java

package com.tutorialspoint.theater;

import java.io.FileOutputStream;
import java.io.IOException;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;

public class TheaterWriter{
   public static void main(String[] args) throws IOException {
      Theater theater = Theater.newBuilder()
         .setName("Silver Screener")
         .setTotalCapacity(2300000000L)
         .build();
		
      String filename = "theater_protobuf_output";
      System.out.println("Saving theater information to file: " + filename);
		
      try(FileOutputStream output = new FileOutputStream(filename)){
         theater.writeTo(output);
      }
	    
      System.out.println("Saved theater information with following data to disk: \n" + theater);
   }
}

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Saving theater information to file: theater_protobuf_output
Saved theater information with following data to disk:
name: "Silver Screener"
total_capacity: 2300000000

假設我們對讀取器使用不同版本的proto檔案:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   string name = 1;
   int64 total_capacity = 2;
}

接下來,我們將有一個reader來讀取theater資訊:

TheaterReader.java

package com.tutorialspoint.theater;

import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import com.google.protobuf.ProtocolStringList;
import com.tutorialspoint.greeting.Greeting.Greet;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.Builder;

public class TheaterReader{
   public static void main(String[] args) throws IOException {
      Builder theaterBuilder = Theater.newBuilder();

      String filename = "theater_protobuf_output";
      System.out.println("Reading from file " + filename);
        
      try(FileInputStream input = new FileInputStream(filename)) {
         Theater theater = theaterBuilder.mergeFrom(input).build();
         System.out.println(theater);
         System.out.println("Unknwon fields: " + theater.getUnknownFields());
      }
   }
}		

現在讓我們執行reader以從同一檔案讀取:

java -cp .\target\protobuf-tutorial-old-1.0.jar com.tutorialspoint.theater.TheaterReader

Reading from file theater_protobuf_output
name: "Silver Screener"
address: "212, Maple Street, LA, California"

所以,這裡沒有什麼新內容,我們只是根據 Protobuf 定義寫了簡單的字串,並且讀取器能夠讀取字串。但是現在,讓我們在 Protobuf 定義中交換序列號,使其如下所示:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   string name = 2;
   int32 total_capacity = 2;
}		

在編譯之前,將 JAR 從之前的編譯重新命名為protobuf-tutorial-old-1.0.jar。然後編譯。

現在,編譯後,讓我們先執行writer

> java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.theater.TheaterWriter

Reading from file theater_protobuf_output
address: "Silver Screener"
total_capcity: -1994967296

從輸出中可以看到,舊的讀取器將數字從int64轉換,但是給定的int32沒有足夠的空間來容納資料,它環繞到負數。此環繞是 Java 特定的,與 Protobuf 無關。

因此,我們需要從int32升級到int64,而不是反過來。如果我們仍然希望從int64轉換為int32,我們需要確保值實際上可以儲存在 31 位中(1 位用於符號位)。

Protocol Buffers - Kafka 整合

我們已經介紹了很多關於 Protocol Buffers 及其資料型別的示例。在本章中,讓我們再舉一個例子,看看 Protocol Buffers 如何與 Kafka 使用的 Schema Registry 整合。讓我們首先了解什麼是“schema registry”。

Schema Registry

Kafka 是最廣泛使用的訊息佇列之一。它用於大規模應用釋出者-訂閱者模型。有關 Kafka 的更多資訊,請訪問此處:https://tutorialspoint.tw/apache_kafka/index.htm

但是,在基本層面上,Kafka生產者應該傳送訊息,即 Kafka消費者可以讀取的資訊片段。而這種訊息的傳送和消費是我們需要 schema 的地方。它在大型組織中尤其需要,在大型組織中,多個團隊讀取/寫入 Kafka 主題。Kafka 提供了一種將此 schema 儲存在schema registry中的方法,這些 schema 隨後在生產者/消費者建立/消費訊息時建立/使用。

維護 schema 有兩個主要好處:

  • 相容性:在大型組織中,必須確保生成訊息的團隊不會破壞使用這些訊息的下游工具。Schema registry 確保更改向後相容。

  • 高效編碼:在每條訊息中傳送欄位名稱及其型別效率低下且計算量大。使用 schema,我們不需要在每條訊息中傳送此資訊。

schema registry 支援Avro、Google Protocol BuffersJSON Schema 作為 schema 語言。這些語言中的 schema 可以儲存在 schema registry 中。在本教程中,我們需要 Kafka 設定和 Schema registry 設定。

有關 Kafka 的安裝,您可以檢視以下連結:

安裝 Kafka 後,您可以透過更新/etc/schema-registry/schema-registry.properties檔案來設定 Schema Registry。

# where should schema registry listen on
listeners=http://0.0.0.0:8081

# Schema registry uses Kafka beneath it, so we need to tell where are the Kafka brokers available
kafkastore.bootstrap.servers=PLAINTEXT://hostname:9092,SSL://hostname2:9092
Once done, you can then run:
sudo systemctl start confluent-schema-registry

設定完成後,讓我們開始將 Google Protocol Buffers 與 Schema Registry 一起使用。

使用 Protocol Buffers Schema 的 Kafka 生產者

讓我們繼續使用我們的劇院示例。我們將使用以下 Protocol Buffers schema:

theater.proto

syntax = "proto3";
package theater;
option java_package = "com.tutorialspoint.theater";

message Theater {
   string name = 1;
   string address = 2;
  
   int32 total_capcity = 3;
   int64 mobile = 4;
   float base_ticket_price = 5;
  
   bool drive_in = 6;
  
   enum PAYMENT_SYSTEM{
      CASH = 0;
      CREDIT_CARD = 1;
      DEBIT_CARD = 2;
      APP = 3;
   }
   PAYMENT_SYSTEM payment = 7;
   repeated string snacks = 8;
   
   map<string, int32> movieTicketPrice = 9;
}

現在,讓我們建立一個簡單的 Kafka寫入器,它會將以這種格式編碼的訊息寫入 Kafka 主題。但要做到這一點,首先我們需要向我們的 Maven POM 新增一些依賴項:

  • Kafka 客戶端用於 Kafka 生產者和消費者

  • Kafka Protocol Buffers 序列化程式用於序列化和反序列化訊息

  • Slf4j simple 確保我們從 Kafka 獲取日誌

<dependency>
   <groupId>org.apache.kafka</groupId>
   <artifactId>kafka-clients</artifactId>
   <version>2.5.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/io.confluent/kafka-protobuf-serializer -->
<dependency>
   <groupId>io.confluent</groupId>
   <artifactId>kafka-protobuf-serializer</artifactId>
   <version>5.5.1</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.slf4j/slf4j-simple -->
<dependency>
   <groupId>org.slf4j</groupId>
   <artifactId>slf4j-simple</artifactId>
   <version>1.7.30</version>
</dependency>

完成此操作後,讓我們現在建立一個 Kafka生產者。此生產者將建立併發送一條訊息,其中包含劇院物件。

KafkaProtbufProducer.java

package com.tutorialspoint.kafka;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.PAYMENT_SYSTEM;

public class KafkaProtbufProducer {
   public static void main(String[] args) throws Exception{
      String topicName = "testy1";
      Properties props = new Properties();
      props.put("bootstrap.servers", "localhost:9092");
      props.put("clientid", "foo");
      props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
      props.put("value.serializer", "io.confluent.kafka.serializers.protobuf.KafkaProtocol BuffersSerializer");
      props.put("schema.registry.url", "https://:8081");
      props.put("auto.register.schemas", "true");
      
      Producer<String, Theater> producer = new KafkaProducer<>(props);
      producer.send(new ProducerRecord<String, Theater>(topicName, "SilverScreen", getTheater())).get();
      System.out.println("Sent to Kafka: \n" + getTheater());
      producer.flush();
      producer.close();
   }
   public static Theater getTheater() {
      List<String> snacks = new ArrayList<>();
      snacks.add("Popcorn");
      snacks.add("Coke");
      snacks.add("Chips");
      snacks.add("Soda");
           
      Map<String, Integer> ticketPrice = new HashMap<>();
      ticketPrice.put("Avengers Endgame", 700);
      ticketPrice.put("Captain America", 200);
      ticketPrice.put("Wonder Woman 1984", 400);
                  
      Theater theater = Theater.newBuilder()
         .setName("Silver Screener")
         .setAddress("212, Maple Street, LA, California")
         .setDriveIn(true)
         .setTotalCapacity(320)
         .setMobile(98234567189L)
         .setBaseTicketPrice(22.45f)
         .setPayment(PAYMENT_SYSTEM.CREDIT_CARD)
         .putAllMovieTicketPrice(ticketPrice)
         .addAllSnacks(snacks)
         .build();
      return theater;
   }
}

以下是我們需要了解的一些要點:

  • 我們需要將 Schema Registry URL 傳遞給生產者。

  • 我們還需要傳遞正確的 Protocol Buffers 序列化程式,該序列化程式特定於 Schema Registry。

  • 當我們完成傳送後,schema registry 會自動儲存劇院物件的 schema。

  • 最後,我們從自動生成的 Java 程式碼建立了一個劇院物件,這就是我們要傳送的內容。

輸出

現在,讓我們編譯並執行程式碼:

mvn clean install ; java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.kafka.KafkaProtbufProducer

我們將看到以下輸出:

[main] INFO org.apache.kafka.common.utils.AppInfoParser - Kafka version: 2.5.0
[main] INFO org.apache.kafka.common.utils.AppInfoParser - Kafka commitId: 66563e712b0b9f84
[main] INFO org.apache.kafka.common.utils.AppInfoParser - Kafka startTimeMs: 1621692205607
[kafka-producer-network-thread | producer-1] INFO org.apache.kafka.clients.Metadata - 
[Producer clientId=producer-1] Cluster ID: 7kwQVXjYSz--bE47MiXmjw

傳送到 Kafka

name: "Silver Screener"
address: "212, Maple Street, LA, California"
total_capacity: 320
mobile: 98234567189
base_ticket_price: 22.45
drive_in: true
payment: CREDIT_CARD
snacks: "Popcorn"
snacks: "Coke"
snacks: "Chips"
snacks: "Soda"
movieTicketPrice {
   key: "Avengers Endgame"
   value: 700
}
movieTicketPrice {
   key: "Captain America"
   value: 200
}
movieTicketPrice {
   key: "Wonder Woman 1984"
   value: 400
}
[main] INFO org.apache.kafka.clients.producer.KafkaProducer - 
[Producer clientId=producer-1] Closing the Kafka producer with timeoutMillis = 9223372036854775807 ms.

這意味著我們的訊息已傳送。

現在,讓我們確認 schema 已儲存在 Schema Registry 中。

curl  -X GET https://:8081/subjects | jq

顯示的輸出是“topicName” + “key/value”

[
   "testy1-value"
]

我們還可以看到 registry 儲存的 schema:

curl  -X GET https://:8081/schemas/ids/1 | jq {
   "schemaType": "PROTOBUF",
   "schema": "syntax = \"proto3\";\npackage theater;\n\noption java_package = \"com.tutorialspoint.theater\";\n\nmessage Theater {
      \n  string name = 1;\n  string address = 2;\n  int64 total_capacity = 3;\n  
      int64 mobile = 4;\n  float base_ticket_price = 5;\n  bool drive_in = 6;\n  
      .theater.Theater.PAYMENT_SYSTEM payment = 7;\n  repeated string snacks = 8;\n  
      repeated .theater.Theater.MovieTicketPriceEntry movieTicketPrice = 9;\n\n  
      message MovieTicketPriceEntry {\n    option map_entry = true;\n  \n    
      string key = 1;\n    int32 value = 2;\n  }\n  enum PAYMENT_SYSTEM {
         \n CASH = 0;\n    CREDIT_CARD = 1;\n    DEBIT_CARD = 2;\n    APP = 3;\n  
      }\n
   }\n"
}

使用 Protocol Buffers Schema 的 Kafka 消費者

現在讓我們建立一個 Kafka消費者。此消費者將使用包含劇院物件的訊息。

KafkaProtbufConsumer.java

package com.tutorialspoint.kafka;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.kafka.clients.producer.Producer;
import org.apache.kafka.clients.producer.KafkaProducer;
import org.apache.kafka.clients.producer.ProducerRecord;
import com.tutorialspoint.theater.TheaterOuterClass.Theater;
import com.tutorialspoint.theater.TheaterOuterClass.Theater.PAYMENT_SYSTEM;

public class KafkaProtbufConsumer {
   public static void main(String[] args) throws Exception{
      String topicName = "testy1";
      Properties props = new Properties();
      props.put("bootstrap.servers", "localhost:9092");
      props.put("clientid", "foo");
      props.put("key.serializer", "org.apache.kafka.common.serialization.StringSerializer");
         
      props.put("value.serializer", "io.confluent.kafka.serializers.protobuf.KafkaProtocol BuffersSerializer");
      props.put("schema.registry.url", "https://:8081");
      props.put("auto.register.schemas", "true");
      Producer<String, Theater> producer = new KafkaProducer<>(props);
      producer.send(new ProducerRecord<String, Theater>(topicName, "SilverScreen", getTheater())).get();
      
      System.out.println("Sent to Kafka: \n" + getTheater());
      producer.flush();
      producer.close();
   }
   public static Theater getTheater() {
      List<String> snacks = new ArrayList<>();
      snacks.add("Popcorn");
      snacks.add("Coke");
      snacks.add("Chips");
      snacks.add("Soda");
           
      Map<String, Integer> ticketPrice = new HashMap<>();
      ticketPrice.put("Avengers Endgame", 700);
      ticketPrice.put("Captain America", 200);
      ticketPrice.put("Wonder Woman 1984", 400);
      
      Theater theater = Theater.newBuilder()
         .setName("Silver Screener")
         .setAddress("212, Maple Street, LA, California")
         .setDriveIn(true)
         .setTotalCapacity(320)
         .setMobile(98234567189L)
         .setBaseTicketPrice(22.45f)
         .setPayment(PAYMENT_SYSTEM.CREDIT_CARD)
         .putAllMovieTicketPrice(ticketPrice)
         .addAllSnacks(snacks)
         .build();
      return theater;
   }
}

以下是我們需要了解的一些要點:

  • 我們需要將 Schema Registry URL 傳遞給消費者。

  • 我們還需要傳遞正確的 Protocol Buffers 反序列化程式,該反序列化程式特定於 Schema Registry。

  • 當我們完成消費後,Schema Registry 會自動讀取儲存的劇院物件的 schema。

  • 最後,我們從自動生成的 Java 程式碼建立了一個劇院物件,這就是我們要傳送的內容。

輸出

現在,讓我們編譯並執行程式碼:

mvn clean install ; java -cp .\target\protobuf-tutorial-1.0.jar com.tutorialspoint.kafka.KafkaProtbufConsumer

offset = 0, key = SilverScreen, value = May 22, 2021 7:50:15 PM com.google.protobuf.TextFormat$Printer$MapEntryAdapter compareTo
May 22, 2021 7:50:15 PM com.google.protobuf.TextFormat$Printer$MapEntryAdapter compareTo

name: "Silver Screener"
address: "212, Maple Street, LA, California"
total_capacity: 320
mobile: 98234567189
base_ticket_price: 22.45
drive_in: true
payment: CREDIT_CARD
snacks: "Popcorn"
snacks: "Coke"
snacks: "Chips"
snacks: "Soda"
movieTicketPrice {
   key: "Captain America"
   value: 200
}
movieTicketPrice {
   key: "Wonder Woman 1984"
   value: 400
}
movieTicketPrice {
   key: "Avengers Endgame"
   value: 700
}

因此,正如我們所看到的,寫入 Kafka 的訊息被消費者正確地使用了。此外,Registry 儲存了可以透過 REST API 訪問的 schema。

Protocol Buffers - 其他語言

我們一直在 Java 和 Python 中使用 Protocol Buffers。但它支援多種語言,包括 C++、C#、Kotlin、Dart、Go 等。基本內容大多保持不變,即編寫proto schema,透過protoc二進位制檔案生成原始碼,我們的程式碼可以使用該原始碼。讓我們在本節中為GoDart編寫一個基本示例。

我們將使用以下proto檔案:

greeting.proto

syntax = "proto3";
package tutorial;
message Greet {
   string greeting = 1;
   string username = 2;
}

在 Go Lang 中使用 Google Protocol Buffers

要使用上述 Protocol Buffers 檔案,我們首先必須為Go語言中的Greet類生成程式碼。為此,我們需要執行以下操作:

安裝Go Protocol Buffers 外掛 (protoc-gen-go),這是我們一直在使用的protoc檔案的先決條件:

go install google.golang.org/protobuf/cmd/protoc-gen-go

然後,使用提供的“.proto”檔案執行protoc,我們將指示它在“go”目錄下生成程式碼。

protoc  --go_out=go proto_files/greeting.proto

執行上述命令後,您會注意到一個自動生成的類:“greeting.pb.go”。此類將幫助我們序列化和反序列化 Greet 物件。

現在,讓我們建立資料的寫入器,它將以使用者名稱問候語作為輸入:

greeting_writer.go

import "fmt"
import "io/ioutil"

func main() {
   greet := Greeting{}
   greet.username = "John"
   greet.greeting = "Hello"
   out, err := proto.Marshal(greet)
    
   ioutil.WriteFile("greeting_go_out", out , 0644)
    
   fmt.Println("Saved greeting with following data to disk:")
   fmt.Println(p)
}

現在讓我們建立將讀取檔案的讀取器

greeting_reader.go

import "fmt"
import "io/ioutil"

func main() {
   in, err := ioutil.ReadFile("greeting_go_out")

   greet := &pb.Greet{}
   proto.Unmarshal(in, greet)

   fmt.Println("Reading from file greeting_protobuf_output:")
   fmt.Println(greet)
}

輸出

讀取器只需從同一檔案讀取,將其反序列化,並列印有關問候語的資料。

現在我們已經設定了讀取器寫入器,讓我們編譯專案。

接下來,讓我們首先執行寫入器

go run greeting_writer.go

Saved greeting with following data to disk:
{greeting: Hello, username: John}

然後,讓我們執行讀取器

go run greeting_reader.go

Reading from file greeting_protobuf_output
{greeting: Hello, username: John}

因此,正如我們所看到的,寫入器序列化並儲存到檔案中的資料,這些資料被讀取器正確地反序列化並相應地打印出來。

在 Dart 中使用 Google Protocol Buffers

要使用上述 Protocol Buffers 檔案,我們首先必須安裝併為 Dart 語言中的Greet類生成程式碼。為此,我們需要執行以下操作:

安裝Dart Protocol Buffers 外掛 (protoc-gen-go),這是我們一直在使用的protoc檔案的先決條件。https://github.com/dart-lang/protobuf/tree/master/protoc_plugin#how-to-build-and-use

然後,使用提供的“.proto”檔案執行protoc,我們將指示它在“dart”目錄下生成程式碼。

protoc  --go_out=dart proto_files/greeting.proto

執行上述命令後,您會注意到一個自動生成的類:“greeting.pb.dart”。此類將幫助我們序列化和反序列化Greet物件。

現在,讓我們建立資料的寫入器,它將以使用者名稱問候語作為輸入:

greeting_writer.dart

import 'dart:io';
import 'dart/greeting.pb.dart';

main(List arguments) {
   Greeting greet = Greeting();
   greet.greeting = "Hello";
   greet.username = "John";
  
   File file = File("greeting_go_out");
  
   print("Saved greeting with following data to disk:")
   file.writeAsBytes(greet.writeToBuffer());
   print(greet)
}

接下來,讓我們建立一個將讀取檔案的讀取器

greeting_reader.dart

import 'dart:io';
import 'dart/greeting.pb.dart';

main(List arguments) {
   File file = File("greeting_go_out");

   print("Reading from file greeting_protobuf_output:")
   Greeting greet = Greeting.fromBuffer(file.readAsBytesSync());
   print(greet)
}

輸出

讀取器只是從同一個檔案讀取,反序列化它,並列印有關問候語的資料。

現在我們已經設定了讀取器寫入器,讓我們編譯專案。

接下來,讓我們首先執行寫入器

dart run greeting_writer.dart

Saved greeting with following data to disk:
greeting: Hello
username: John

然後,讓我們執行讀取器

dart run greeting_reader.dart

Reading from file greeting_protobuf_output
greeting: Hello
username: John

所以,正如我們所看到的,由寫入器序列化並儲存到檔案中的資料,這些資料被讀取器正確地反序列化並相應地打印出來。

廣告

© . All rights reserved.