Note/ProtoBuf.md

321 lines
10 KiB
Markdown
Raw Normal View History

2024-09-09 15:14:36 +08:00
# 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)