C#gRPC文件流,原始文件比流文件小
我在设置请求流类型的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
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构造函数是否有一个重载,它需要一个长度?