简体   繁体   English

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

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

I am having some problems with setting up a request-stream type gRPC architecture.我在设置请求流类型 gRPC 架构时遇到了一些问题。 The code below is just for testing purposes and it is missing various validation checks, but the main issue is that the original file is always smaller than the received one.下面的代码仅用于测试目的,它缺少各种验证检查,但主要问题是原始文件总是小于收到的文件。

Could the cause here be encoding?这里的原因可能是编码吗? It doesn't matter what the file type is, the end result is always that the file sizes are different.文件类型是什么并不重要,最终结果总是文件大小不同。

Protobuf inteface : 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;
}

Server side (sending):服务器端(发送):

    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);
        }            
    }

Client side (receiving):客户端(接收):

    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);
        }
    }

To add to Marc's answer, I feel like you can simplify your code a little bit.要补充 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]),
     });
}

I've used the array range operator from C# 8.0 which makes this cleaner or you can also use the overload of ByteString.CopyFrom which takes in an offset and count of how many bytes to include.我使用了 C# 8.0 中的数组范围运算符,它使这个更清晰,或者您也可以使用ByteString.CopyFrom的重载,它接收偏移量和要包含的字节数。

In your write loop, the chunk you actually send is for the oversized buffer, not accounting for length .在您的写循环中,您实际发送的块用于超大缓冲区,而不是length This means that the last segment includes some garbage and is oversized.这意味着最后一段包含一些垃圾并且过大。 The received payload will be oversized by this same amount.接收到的有效载荷将超大相同的数量。 So: make sure you account for length when constructing the chunk to send.所以:确保在构造要发送的块时考虑length

I tested the code and modified it to transfer the correct size.我测试了代码并修改了它以传输正确的大小。

The complete code is available at the following URL: https://github.com/lisa3907/grpc.fileTransfer完整代码可在以下 URL 获得: https : //github.com/lisa3907/grpc.fileTransfer

server-side-code服务器端代码

 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);
 }

client-side-code客户端代码

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