You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用protobuf-net跨进程读写protobuf文件的不完整对象处理问题

解决protobuf-net跨进程读写不完整数据的问题

问题1:高效判断记录是否完整的方法

因为你使用的是Fixed32BigEndian类型的长度前缀,每个序列化记录的结构是4字节大端长度值 + 实际protobuf数据。可以通过预检查流的剩余字节数来判断记录是否完整,避免提前触发反序列化异常:

  1. 先检查当前流的剩余字节(fileStream.Length - fileStream.Position)是否至少有4字节(长度前缀的大小),如果不足则等待;
  2. 读取4字节的长度前缀,转换为大端整数,得到实际protobuf数据的长度;
  3. 再次检查剩余字节是否≥这个数据长度,如果不足则将流的位置回退到读取前缀之前,等待新数据写入;
  4. 若字节数足够,再执行反序列化操作。

修改后的进程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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.21 18:33:14