繁体   English   中英

C# gRPC 文件流式传输,原始文件小于流式传输的文件

[英]C# gRPC file streaming, original file smaller than the streamed one

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

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

Protobuf 接口

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的重载,它接收偏移量和要包含的字节数。

在您的写循环中,您实际发送的块用于超大缓冲区,而不是length 这意味着最后一段包含一些垃圾并且过大。 接收到的有效载荷将超大相同的数量。 所以:确保在构造要发送的块时考虑length

我测试了代码并修改了它以传输正确的大小。

完整代码可在以下 URL 获得: https : //github.com/lisa3907/grpc.fileTransfer

服务器端代码

 while (_offset < _file_bytes.Length)
 {
    if (context.CancellationToken.IsCancellationRequested)
    break;
 
    var _length = Math.Min(_chunk_size, _file_bytes.Length - _offset);
    Buffer.BlockCopy(_file_bytes, _offset, _file_chunk, 0, _length);
 
    _offset += _length;
 
    _chunk.ChunkSize = _length;
    _chunk.Chunk = ByteString.CopyFrom(_file_chunk);
 
    await responseStream.WriteAsync(_chunk).ConfigureAwait(false);
 }

客户端代码

await foreach (var _chunk in _call.ResponseStream.ReadAllAsync().ConfigureAwait(false))
{
    var _total_size = _chunk.FileSize;

    if (!String.IsNullOrEmpty(_chunk.FileName))
    {
       _final_file = _chunk.FileName;
    }

    if (_chunk.Chunk.Length == _chunk.ChunkSize)
       _fs.Write(_chunk.Chunk.ToByteArray());
    else
    {
       _fs.Write(_chunk.Chunk.ToByteArray(), 0, _chunk.ChunkSize);
       Console.WriteLine($"final chunk size: {_chunk.ChunkSize}");
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM