在Flutter中处理protobuf半包问题,主要通过以下方式:
1. 使用Length-Delimited格式
这是最常用的方法,在发送数据前先写入数据长度:
// 发送数据
Uint8List data = yourProtobufObject.writeToBuffer();
List<int> packet = []
..addAll(ByteData(4)..setUint32(0, data.length, Endian.big)..buffer.asUint8List())
..addAll(data);
socket.add(packet);
// 接收数据
List<int> buffer = [];
bool isReading = false;
int expectedLength = 0;
int currentLength = 0;
void onData(List<int> data) {
buffer.addAll(data);
while (buffer.length >= 4) {
if (!isReading) {
// 读取数据长度
var lengthData = buffer.sublist(0, 4);
expectedLength = ByteData.sublistView(lengthData).getUint32(0, Endian.big);
buffer = buffer.sublist(4);
isReading = true;
currentLength = 0;
}
if (buffer.length >= expectedLength) {
// 读取完整数据包
var packetData = buffer.sublist(0, expectedLength);
buffer = buffer.sublist(expectedLength);
// 解析protobuf
YourProtobufMessage message = YourProtobufMessage.fromBuffer(packetData);
handleMessage(message);
isReading = false;
} else {
break;
}
}
}
2. 使用固定分隔符
在数据包末尾添加特殊分隔符:
// 发送
Uint8List data = yourProtobufObject.writeToBuffer();
List<int> packet = [...data, 0xFF, 0xFF]; // 使用0xFFFF作为分隔符
socket.add(packet);
// 接收
List<int> buffer = [];
void onData(List<int> data) {
buffer.addAll(data);
int separatorIndex = findSeparator(buffer);
while (separatorIndex != -1) {
var packetData = buffer.sublist(0, separatorIndex);
buffer = buffer.sublist(separatorIndex + 2); // 跳过分隔符
YourProtobufMessage message = YourProtobufMessage.fromBuffer(packetData);
handleMessage(message);
separatorIndex = findSeparator(buffer);
}
}
int findSeparator(List<int> data) {
for (int i = 0; i < data.length - 1; i++) {
if (data[i] == 0xFF && data[i + 1] == 0xFF) {
return i;
}
}
return -1;
}
3. 使用现成的网络库
推荐使用成熟的网络库处理半包问题:
dependencies:
web_socket_channel: ^2.4.0
socket_io_client: ^2.0.3
关键要点
- 长度前缀法是最可靠的方式
- 缓冲区管理要处理好边界情况
- 考虑使用Stream转换器来封装解包逻辑
- 测试时要模拟网络延迟和分包情况
建议优先采用Length-Delimited格式,这是处理protobuf半包问题最标准和可靠的方法。