# ProtoBuf简明教程 ## 1.什么是Protobuf Protobuf 是一个无关语言,无关平台的,用于序列化结构 化数据的工具。相较于JSON体积更小,传输更快。 Protobuf 定义在,proto文件中,在特定语言进行编译时,进行动态编译。 1. 序列化:将数据结构转换为字节流,便于网络传输和存储。 2. 高效性:相比于JSON,Protobuf 序列化后的字节流更小,传输更快。 3. 兼容性:Protobuf 支持多种语言,使得数据在不同语言之间进行通信和交互更加方便。 4. 可读性:Protobuf 的定义文件是纯文本 ## 2.主要应用场景 1. 网络通信:Protobuf 适用于网络通信场景,例如在分布式系统中进行数据传输和通信。 2. 数据存储:Protobuf 可以将数据结构存储在文件或数据库中,使得数据的存储和检索更加高效。 3. 配置文件:Protobuf 可以用于存储和传输配置信息,例如应用程序的配置参数。 ## 3.Java中使用 创建一个Maven项目其中使用 Java 作为 Client 端,Python 作为 Server 端 ```xml com.google.protobuf protobuf-java 4.27.2 ``` 在项目中创建文件夹 `script` 在其下创建 Protobuf 文件 `video_info.proto` 其中内容如下 ```protobuf syntax = "proto3"; message VideoFeature { optional int32 author_gender = 1 ; optional int64 channel_id = 2; } ``` 然后导入对应的 `protoc` 工程文件,[下载对应版本的文件](https://github.com/protocolbuffers/protobuf/releases/tag/v27.2) 并解压到`script`目录下 然后创建一个生成脚本 `build_pb.sh`内容如下 ```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`,其中内容如下 ```java 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(); } } ``` ```python 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](https://lsky.hhdxw.top/imghub/2024/07/image-202407291722220767.png) ## 4.测试 ```protobuf 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 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` ```java 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(); } } ``` ```python 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](https://lsky.hhdxw.top/imghub/2024/07/image-202407291722220780.png)