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.com/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://127.0.0.1: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://127.0.0.1:8081/subjects | jq

显示的输出是“topicName” + “key/value”

[
   "testy1-value"
]

我们还可以看到 registry 存储的 schema:

curl  -X GET https://127.0.0.1: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://127.0.0.1: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

所以,正如我们所看到的,由写入器序列化并保存到文件中的数据,这些数据被读取器正确地反序列化并相应地打印出来。

广告