Note/ProtoBuf.md

10 KiB
Raw Blame History

ProtoBuf简明教程

1.什么是Protobuf

Protobuf 是一个无关语言,无关平台的,用于序列化结构 化数据的工具。相较于JSON体积更小传输更快。 Protobuf 定义在,proto文件中在特定语言进行编译时进行动态编译。

  1. 序列化:将数据结构转换为字节流,便于网络传输和存储。
  2. 高效性相比于JSONProtobuf 序列化后的字节流更小,传输更快。
  3. 兼容性Protobuf 支持多种语言,使得数据在不同语言之间进行通信和交互更加方便。
  4. 可读性Protobuf 的定义文件是纯文本

2.主要应用场景

  1. 网络通信Protobuf 适用于网络通信场景,例如在分布式系统中进行数据传输和通信。
  2. 数据存储Protobuf 可以将数据结构存储在文件或数据库中,使得数据的存储和检索更加高效。
  3. 配置文件Protobuf 可以用于存储和传输配置信息,例如应用程序的配置参数。

3.Java中使用

创建一个Maven项目其中使用 Java 作为 Client 端Python 作为 Server 端

<dependency>
    <groupId>com.google.protobuf</groupId>
    <artifactId>protobuf-java</artifactId>
    <version>4.27.2</version>
</dependency>

在项目中创建文件夹 script 在其下创建 Protobuf 文件 video_info.proto

其中内容如下

syntax = "proto3";

message VideoFeature {
  optional int32 author_gender = 1 ;
  optional int64 channel_id = 2;
}

然后导入对应的 protoc 工程文件,下载对应版本的文件 并解压到script目录下

然后创建一个生成脚本 build_pb.sh内容如下

#!/bin/bash
SRC_DIR="."
JAVA_DST_DIR="../src/main/java"
PYTHON_DST_DIR="../src/main/python"

./protoc-27.2-osx-aarch_64/bin/protoc -I=$SRC_DIR --java_out=$JAVA_DST_DIR $SRC_DIR/AllTypes.proto
./protoc-27.2-osx-aarch_64/bin/protoc -I=$SRC_DIR --python_out=$PYTHON_DST_DIR $SRC_DIR/AllTypes.proto

其中最后面的文件名是上面创建的proto文件的名称运行sh脚本

能够生成两个文件 VideoInfo.java video_info_pb2.py

然后我们创建两个运行文件 Client.java Server.py,其中内容如下

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * ClassName: Client
 * Package: com.yovinchen.protobuf
 *
 * @author yovinchen
 * @since 2024/7/25 上午9:33
 */

public class Client {

    public static byte[] msg;

    static {
        VideoInfo.VideoFeature feature = VideoInfo.VideoFeature.newBuilder()
                .setAuthorGender(123)
                .setChannelId(321)
                .build();
        msg = feature.toByteArray();

//         msg = "测试字符串".getBytes();
//        msg = "{\"author_gender\":123,\"channel_id\":321}".getBytes();
    }

    public static void main(String[] args) throws IOException {
        System.out.println("客户端启动...");
        // 创建一个流套接字并将其连接到指定主机上的指定端口号
        Socket socket = new Socket("localhost", 8001);
        // 向服务器端发送数据
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());
        out.write(msg);
        out.close();
        socket.close();
    }
}
import socket

import video_info_pb2


def parse(buf):
    try:
        video_feature = video_info_pb2.VideoFeature()
        video_feature.ParseFromString(buf)
        return video_feature
    except Exception:
        return "暂时不支持转换"


if __name__ == "__main__":
    print("Server is starting")
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost', 8001))  # 配置soket绑定IP地址和端口号
    sock.listen(5)  # 设置最大允许连接数
    while True:  # 循环轮询socket状态等待访问
        connection, address = sock.accept()
        buf = connection.recv(1024)
        print(f"原始数据:{buf}")
        print(f"数据长度:{len(buf)}")

        print(parse(buf))
        connection.close()

然后先运行 Server.py 然后再运行 Client.java然后就能在Server看到原始数据以及解析出来的数据。

解除msg中的注释测试Json进行数据传输的数据长度

image-20240729103528251

4.测试

syntax = "proto3";

// 定义一个消息,该消息包含所有基本的数据类型。
message AllTypes {
  // 布尔类型
  bool bool_field = 1;  // 布尔值

  // 字符串类型
  string string_field = 2;  // UTF-8 编码的字符串

  // 字节流类型
  bytes bytes_field = 3;  // 原始字节流

  // 整数类型
  int32 int32_field = 4;  // 32位有符号整数
  int64 int64_field = 5;  // 64位有符号整数
  uint32 uint32_field = 6;  // 32位无符号整数
  uint64 uint64_field = 7;  // 64位无符号整数
  sint32 sint32_field = 8;  // 32位有符号整数使用 zigzag 编码
  sint64 sint64_field = 9;  // 64位有符号整数使用 zigzag 编码

  // 浮点数类型
  float float_field = 14;  // 单精度浮点数
  double double_field = 15;  // 双精度浮点数

  // 固定宽度整数类型
  fixed32 fixed32_field = 10;  // 32位无符号整数小端存储
  fixed64 fixed64_field = 11;  // 64位无符号整数小端存储
  sfixed32 sfixed32_field = 12;  // 32位有符号整数小端存储
  sfixed64 sfixed64_field = 13;  // 64位有符号整数小端存储

  // 重复字段类型
  repeated int32 repeated_int32_field = 31;  // 可以包含多个元素的 int32 字段

  // 映射字段类型
  map<int32, string> map_int32_string_field = 32;  // 键为 int32值为 string 的映射

  // 枚举类型
  EnumType enum_field = 33;  // 枚举类型字段

  // 嵌套消息类型
  MessageType nested_message_field = 34;  // 另一个消息类型的字段

  // 嵌套的消息类型定义
  message MessageType {
    string nested_string_field = 1;  // 嵌套消息中的字符串字段
  }

  // 枚举类型定义
  enum EnumType {
    ENUM_VALUE_0 = 0;  // 枚举值 0
    ENUM_VALUE_1 = 1;  // 枚举值 1
    ENUM_VALUE_2 = 2;  // 枚举值 2
  }
}

// 以下是用于包装基本类型的特殊消息类型,它们允许携带额外的元数据,如 null 值。
message BoolValue {bool value = 1;}  // 包装布尔值
message StringValue {string value = 1;}  // 包装字符串值
message BytesValue {bytes value = 1;}  // 包装字节流值
message Int32Value {int32 value = 1;}  // 包装 32 位整数值
message Int64Value {int64 value = 1;}  // 包装 64 位整数值
message UInt32Value {uint32 value = 1;}  // 包装无符号 32 位整数值
message UInt64Value {uint64 value = 1;}  // 包装无符号 64 位整数值
message SInt32Value {sint32 value = 1;}  // 包装 zigzag 编码的 32 位整数值
message SInt64Value {sint64 value = 1;}  // 包装 zigzag 编码的 64 位整数值
message Fixed32Value {fixed32 value = 1;}  // 包装小端存储的 32 位整数值
message Fixed64Value {fixed64 value = 1;}  // 包装小端存储的 64 位整数值
message SFixed32Value {sfixed32 value = 1;}  // 包装小端存储的 32 位有符号整数值
message SFixed64Value {sfixed64 value = 1;}  // 包装小端存储的 64 位有符号整数值
message FloatValue {float value = 1;}  // 包装单精度浮点数值
message DoubleValue {double value = 1;}  // 包装双精度浮点数值

服务端和接收端AllTypesClient.java AllTypeServer.py

import com.google.protobuf.ByteString;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

/**
 * ClassName: Client
 * Package: com.yovinchen.protobuf
 *
 * @author yovinchen
 * @since 2024/7/25 上午9:33
 */
public class AllTypesClient {

    public static void main(String[] args) throws IOException {
        System.out.println("客户端启动...");

        // 创建一个 AllTypes 消息实例
        AllTypesOuterClass.AllTypes.Builder builder = AllTypesOuterClass.AllTypes.newBuilder();
        builder.setBoolField(true);
        builder.setStringField("测试字符串");
        builder.setBytesField(ByteString.copyFromUtf8("字节流"));
        builder.setInt32Field(123);
        builder.setInt64Field(123L);
        builder.setUint32Field(456);
        builder.setUint64Field(456L);
        builder.setSint32Field(-123);
        builder.setSint64Field(-123L);
        builder.setFixed32Field(123);
        builder.setFixed64Field(123L);
        builder.setSfixed32Field(-123);
        builder.setSfixed64Field(-123L);
        builder.setFloatField(123.45f);
        builder.setDoubleField(123.45);
        builder.addRepeatedInt32Field(1);
        builder.addRepeatedInt32Field(2);
        builder.putMapInt32StringField(1, "value1");
        builder.putMapInt32StringField(2, "value2");
        builder.setEnumField(AllTypesOuterClass.AllTypes.EnumType.ENUM_VALUE_1);
        builder.setNestedMessageField(AllTypesOuterClass.AllTypes.MessageType.newBuilder()
                .setNestedStringField("嵌套字符串")
                .build());

        // 构建消息
        AllTypesOuterClass.AllTypes allTypesMsg = builder.build();

        // 创建一个流套接字并将其连接到指定主机上的指定端口号
        Socket socket = new Socket("localhost", 8001);

        // 向服务器端发送数据
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());
        out.write(allTypesMsg.toByteArray());

        out.close();
        socket.close();
    }
}
import socket

import AllTypes_pb2


def parse(buf):
    try:
        all_types_msg = AllTypes_pb2.AllTypes()  # 创建 AllTypes 消息实例
        all_types_msg.ParseFromString(buf)  # 从字节流中解析消息
        return all_types_msg  # 返回解析后的消息实例
    except Exception as e:
        print(f"Error parsing message: {e}")
        return None  # 如果解析失败,返回 None 或者自定义的错误信息


if __name__ == "__main__":
    print("Server is starting")
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(('localhost', 8001))
    sock.listen(5)

    while True:
        connection, address = sock.accept()
        buf = connection.recv(1024)
        print(f"原始数据: {buf}")
        print(f"数据长度:{len(buf)}")

        parsed_msg = parse(buf)
        if parsed_msg is not None:
            print(parsed_msg)  # 输出解析后的消息
        else:
            print("无法解析消息")

        connection.close()

image-20240729103332905