C# gRPC 文件流式传输,原始文件小于流式传输的文件
Posted
技术标签:
【中文标题】C# gRPC 文件流式传输,原始文件小于流式传输的文件【英文标题】:C# gRPC file streaming, original file smaller than the streamed one 【发布时间】:2020-03-12 20:26:55 【问题描述】:我在设置请求流类型 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);
【问题讨论】:
您好 - 我可以澄清一下吗?您已标记此 protobuf-net,但显示的代码看起来不像 protobuf-net / protobuf-net.Grpc ;我可以检查一下:您在这里使用的是普通的 Google API,是吗? 在您的发送代码中,您每次将length
写入控制台(或其他),并每次写入所有接收到的长度:它们匹配吗?
我还注意到您发送的块实际上并不依赖于length
,这听起来很糟糕。是否存在需要长度的 ByteString 构造函数的重载?
【参考方案1】:
为了补充 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
的重载,它接受偏移量和要包含的字节数。
【讨论】:
【参考方案2】:在您的写入循环中,您实际发送的块用于超大缓冲区,而不是 length
。这意味着最后一段包含一些垃圾并且过大。接收到的有效载荷将超出相同数量。所以:确保在构造要发送的块时考虑length
。
【讨论】:
【参考方案3】:我测试了代码并修改它以传输正确的大小。
完整代码可在以下网址获得: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");
【讨论】:
以上是关于C# gRPC 文件流式传输,原始文件小于流式传输的文件的主要内容,如果未能解决你的问题,请参考以下文章