使用protobuf-net跨进程读写protobuf文件的不完整对象处理问题
解决protobuf-net跨进程读写不完整数据的问题
问题1:高效判断记录是否完整的方法
因为你使用的是Fixed32BigEndian类型的长度前缀,每个序列化记录的结构是4字节大端长度值 + 实际protobuf数据。可以通过预检查流的剩余字节数来判断记录是否完整,避免提前触发反序列化异常:
- 先检查当前流的剩余字节(
fileStream.Length - fileStream.Position)是否至少有4字节(长度前缀的大小),如果不足则等待; - 读取4字节的长度前缀,转换为大端整数,得到实际protobuf数据的长度;
- 再次检查剩余字节是否≥这个数据长度,如果不足则将流的位置回退到读取前缀之前,等待新数据写入;
- 若字节数足够,再执行反序列化操作。
修改后的进程B读取代码示例:
Task.Factory.StartNew(async () => { using (var fileStream = new FileStream(@"d:\testWriting\test.out", FileMode.Open, FileAccess.Read, FileShare.ReadWrite)) { Person person = null; var buffer = new byte[4]; // 用于读取长度前缀 while (true) { // 检查是否能读取完整的长度前缀 if (fileStream.Length - fileStream.Position < 4) { await Task.Delay(100); continue; } // 读取长度前缀并转换为大端整数 fileStream.Read(buffer, 0, 4); int dataLength = BitConverter.ToInt32(buffer.Reverse().ToArray(), 0); // BigEndian转小端 // 检查是否能读取完整的protobuf数据 if (fileStream.Length - fileStream.Position < dataLength) { fileStream.Position -= 4; // 回退到前缀之前,下次重新读取 await Task.Delay(100); continue; } // 反序列化完整的记录 fileStream.Position -= 4; // 回退前缀位置,让DeserializeWithLengthPrefix读取完整前缀+数据 person = Serializer.DeserializeWithLengthPrefix<Person>(fileStream, PrefixStyle.Fixed32BigEndian); if (person != null) { Console.Write($"\r{person.Id}"); } } } }, TaskCreationOptions.LongRunning);
注意:进程B的FileStream建议使用FileAccess.Read而非ReadWrite,只保留读取权限可减少权限冲突风险。
问题2:使用ProtoReader处理这类异常的方式
ProtoReader没有内置的“自动跳过不完整数据”选项,但可以手动控制读取流程,结合长度检查避免异常,或捕获异常后回退流位置:
方式1:手动用ProtoReader检查并读取
通过ProtoReader.TryReadLengthPrefix方法尝试读取长度前缀,再判断数据是否完整:
using (var protoReader = new ProtoReader(fileStream, null, null)) { while (true) { int dataLength; // 尝试读取Fixed32BigEndian类型的长度前缀 if (!ProtoReader.TryReadLengthPrefix(protoReader, out dataLength, PrefixStyle.Fixed32BigEndian)) { // 无法读取完整前缀,等待并回退位置 await Task.Delay(100); protoReader.Position = fileStream.Position; continue; } // 检查剩余字节是否足够 if (fileStream.Length - fileStream.Position < dataLength) { // 回退前缀长度的位置 protoReader.Position -= 4; fileStream.Position = protoReader.Position; await Task.Delay(100); continue; } // 反序列化对象 Person person = ProtoReader.Deserialize<Person>(protoReader); Console.Write($"\r{person.Id}"); } }
方式2:捕获异常并回退
如果不想提前检查,也可以捕获反序列化时抛出的EndOfStreamException和ProtoException,回退流位置后等待:
while (true) { long preReadPosition = fileStream.Position; try { person = Serializer.DeserializeWithLengthPrefix<Person>(fileStream, PrefixStyle.Fixed32BigEndian); if (person != null) { Console.Write($"\r{person.Id}"); } } catch (EndOfStreamException) { fileStream.Position = preReadPosition; await Task.Delay(100); } catch (ProtoException) { fileStream.Position = preReadPosition; await Task.Delay(100); } }
这种方式的缺点是异常处理有一定性能开销,不如提前检查高效。
内容的提问来源于stack exchange,提问作者NickMan
相关产品推荐
相关产品推荐

