Note/ProtoBuf.md

321 lines
10 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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 端
```xml
<dependency>
<groupId>com.google.protobuf</groupId>
<artifactId>protobuf-java</artifactId>
<version>4.27.2</version>
</dependency>
```
在项目中创建文件夹 `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<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`
```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)