C#gRPC文件流,原始文件比流文件小

C#gRPC文件流,原始文件比流文件小,c#,stream,grpc,protobuf-net,C#,Stream,Grpc,Protobuf Net,我在设置请求流类型的gRPC体系结构时遇到一些问题。下面的代码仅用于测试目的,它缺少各种验证检查,但主要问题是原始文件总是比收到的文件小 这里的原因可能是编码吗?不管文件类型是什么,最终的结果总是文件大小不同 协议接口: syntax = "proto3"; package FileTransfer; option csharp_namespace = "FileTransferProto"; service FileTransferService { rpc DownloadFi

我在设置请求流类型的gRPC体系结构时遇到一些问题。下面的代码仅用于测试目的,它缺少各种验证检查,但主要问题是原始文件总是比收到的文件小

这里的原因可能是编码吗?不管文件类型是什么,最终的结果总是文件大小不同

协议接口

syntax = "proto3";
package FileTransfer;
option csharp_namespace = "FileTransferProto";

service FileTransferService {  
    rpc DownloadFile(FileRequest) returns (stream ChunkMsg);
}  

message ChunkMsg {
    string FileName = 1;
    int64 FileSize = 2;
    bytes Chunk = 3;
}

message FileRequest {
    string FilePath = 1;
}
服务器端(发送):

    public override async Task DownloadFile(FileRequest request, IServerStreamWriter<ChunkMsg> responseStream, ServerCallContext context)
    {
        string filePath = request.FilePath;

        if (!File.Exists(filePath)) { return; }

        FileInfo fileInfo = new FileInfo(filePath);

        ChunkMsg chunk = new ChunkMsg();
        chunk.FileName = Path.GetFileName(filePath);
        chunk.FileSize = fileInfo.Length;

        int fileChunkSize = 64 * 1024;

        byte[] fileByteArray = File.ReadAllBytes(filePath);
        byte[] fileChunk = new byte[fileChunkSize];
        int fileOffset = 0;

        while (fileOffset < fileByteArray.Length && !context.CancellationToken.IsCancellationRequested)
        {
            int length = Math.Min(fileChunkSize, fileByteArray.Length - fileOffset);
            Buffer.BlockCopy(fileByteArray, fileOffset, fileChunk, 0, length);
            fileOffset += length;
            ByteString byteString = ByteString.CopyFrom(fileChunk);

            chunk.Chunk = byteString;
            await responseStream.WriteAsync(chunk).ConfigureAwait(false);
        }            
    }
    public static async Task GetFile(string filePath)
    {
        var channel = Grpc.Net.Client.GrpcChannel.ForAddress("https://localhost:5001/", new GrpcChannelOptions
        {
            MaxReceiveMessageSize = 5 * 1024 * 1024, // 5 MB
            MaxSendMessageSize = 5 * 1024 * 1024, // 5 MB
        });

        var client = new FileTransferProto.FileTransferService.FileTransferServiceClient(channel);

        var request = new FileRequest { FilePath = filePath };
        string tempFileName = $"temp_{DateTime.UtcNow.ToString("yyyyMMdd_HHmmss")}.tmp";
        string finalFileName = tempFileName;

        using (var call = client.DownloadFile(request))
        {
            await using (Stream fs = File.OpenWrite(tempFileName))
            {
                await foreach (ChunkMsg chunkMsg in call.ResponseStream.ReadAllAsync().ConfigureAwait(false))
                {
                    Int64 totalSize = chunkMsg.FileSize;
                    string tempFinalFilePath = chunkMsg.FileName;

                    if (!string.IsNullOrEmpty(tempFinalFilePath))
                    {
                        finalFileName = chunkMsg.FileName;
                    }

                    fs.Write(chunkMsg.Chunk.ToByteArray());
                }
            }
        }

        if (finalFileName != tempFileName)
        {
            File.Move(tempFileName, finalFileName);
        }
    }

在写循环中,实际发送的数据块用于超大缓冲区,而不是
长度
。这意味着最后一段包含一些垃圾,并且尺寸过大。接收到的有效负载将超出相同的大小。因此:在构建要发送的块时,请确保考虑
长度

要补充Marc的答案,我觉得您可以稍微简化代码

using var fs = File.Open(filePath, System.IO.FileMode.Open);
int bytesRead;
var buffer = new byte[fileChunkSize];
while ((bytesRead = await fs.ReadAsync(buffer)) > 0)
{
     await call.RequestStream.WriteAsync(new ChunkMsg
     {
          // Here the correct number of bytes must be sent which is starting from
          // index 0 up to the number of read bytes from the file stream.
          // If you solely pass 'buffer' here, the same bug would be present.
          Chunk = ByteString.CopyFrom(buffer[0..bytesRead]),
     });
}

我使用了C#8.0中的数组范围运算符,这使它更干净,或者您也可以使用
ByteString.CopyFrom的重载,它接受偏移量和要包含多少字节的计数。

我测试了代码并对其进行了修改,以传输正确的大小

完整的代码可从以下URL获得:

服务器端代码

while(\u offset<\u file\u bytes.Length)
{
if(context.CancellationToken.IsCancellationRequested)
打破
var\u length=Math.Min(\u chunk\u size,\u file\u bytes.length-\u offset);
BlockCopy(_文件字节,_偏移量,_文件块,0,_长度);
_偏移量+=\u长度;
_chunk.ChunkSize=\u长度;
_chunk.chunk=ByteString.CopyFrom(\u file\u chunk);
wait responseStream.WriteAsync(_chunk).ConfigureWait(false);
}
客户端代码

await foreach(call.ResponseStream.ReadAllAsync().ConfigureAwait(false)中的var\u块)
{
var\u total\u size=\u chunk.FileSize;
如果(!String.IsNullOrEmpty(_chunk.FileName))
{
_final_file=_chunk.FileName;
}
if(_chunk.chunk.Length==_chunk.ChunkSize)
_写入(_chunk.chunk.ToByteArray());
其他的
{
_写入(_chunk.chunk.ToByteArray(),0,_chunk.ChunkSize);
WriteLine($“最终块大小:{u chunk.ChunkSize}”);
}
}

你好-我能澄清一下吗?您已经标记了它,但是显示的代码看起来不像protobuf-net/protobuf-net.Grpc;我可以检查一下吗:你在这里使用的是普通的谷歌API,是吗?在你的发送代码中,你每次都要把
长度写到控制台(或其他什么),然后每次都写下所有接收到的长度:它们匹配吗?我还注意到你发送的数据块实际上并不依赖于
长度,这听起来很糟糕。ByteString构造函数是否有一个重载,它需要一个长度?