C# 如何在执行流式处理操作时实现更好的粒度?
好的,我正在使用我的文件传输服务,我可以通过WCF流传输文件。我有很好的速度,而且我最终能够得到很好的简历支持,因为我在流媒体之前会把我的文件分成小块 然而,在测量消息流化和写入时的详细传输速度时,我遇到了服务器端传输和客户端接收的问题 下面是文件分块的代码,服务每次需要向客户端发送另一个分块时都会调用该代码C# 如何在执行流式处理操作时实现更好的粒度?,c#,wcf,streaming,bytebuffer,C#,Wcf,Streaming,Bytebuffer,好的,我正在使用我的文件传输服务,我可以通过WCF流传输文件。我有很好的速度,而且我最终能够得到很好的简历支持,因为我在流媒体之前会把我的文件分成小块 然而,在测量消息流化和写入时的详细传输速度时,我遇到了服务器端传输和客户端接收的问题 下面是文件分块的代码,服务每次需要向客户端发送另一个分块时都会调用该代码 public byte[] NextChunk() { if (MoreChunks) // If there are more chunks, proce
public byte[] NextChunk()
{
if (MoreChunks) // If there are more chunks, procede with the next chunking operation, otherwise throw an exception.
{
byte[] buffer;
using (BinaryReader reader = new BinaryReader(File.OpenRead(FilePath)))
{
reader.BaseStream.Position = currentPosition;
buffer = reader.ReadBytes((int)MaximumChunkSize);
}
currentPosition += buffer.LongLength; // Sets the stream position to be used for the next call.
return buffer;
}
else
throw new InvalidOperationException("The last chunk of the file has already been returned.");
在上面,我基本上是根据我使用的块大小写入缓冲区的(在本例中,我发现它是2mb,与较大或较小的块大小相比,它具有最佳的传输速度)。然后,我做了一些工作来记住我在哪里中断了,并返回缓冲区
以下代码是服务器端的工作
public FileMessage ReceiveFile()
{
if (!transferSpeedTimer.Enabled)
transferSpeedTimer.Start();
byte[] buffer = chunkedFile.NextChunk();
FileMessage message = new FileMessage();
message.FileMetaData = new FileMetaData(chunkedFile.MoreChunks, buffer.LongLength);
message.ChunkData = new MemoryStream(buffer);
if (!chunkedFile.MoreChunks)
{
OnTransferComplete(this, EventArgs.Empty);
Timer timer = new Timer(20000f);
timer.Elapsed += (sender, e) =>
{
StopSession();
timer.Stop();
};
timer.Start();
}
//This needs to be more granular. This method is called too infrequently for a fast and accurate enough progress of the file transfer to be determined.
TotalBytesTransferred += buffer.LongLength;
return message;
}
hostChannel = channelFactory.CreateChannel();
((IContextChannel)hostChannel).OperationTimeout = new TimeSpan(3, 0, 0);
bool moreChunks = true;
using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(fileWritePath)))
{
writer.BaseStream.SetLength(0);
while (moreChunks)
{
FileMessage message = hostChannel.ReceiveFile();
moreChunks = message.FileMetaData.MoreChunks;
UpdateTotalBytesTransferred(message);
writer.BaseStream.Position = filePosition;
message.ChunkData.CopyTo(writer.BaseStream);
TotalBytesTransferred = message.FileMetaData.FilePosition;
filePosition += message.FileMetaData.ChunkLength;
}
OnTransferComplete(this, EventArgs.Empty);
}
在这个方法中,客户端在WCF调用中调用该方法,我获取下一个块的信息,创建消息,使用计时器执行一点操作,在传输完成后停止会话,并更新传输速度。在返回消息前不久,我用缓冲区的长度增加我的TotalByTestTransferred
,它用于帮助我计算传输速度
if (Role == FileTransferItem.FileTransferRole.Receiver)
{
hostChannel = channelFactory.CreateChannel();
((IContextChannel)hostChannel).OperationTimeout = new TimeSpan(3, 0, 0);
bool moreChunks = true;
long bytesPreviousPosition = 0;
using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(fileWritePath)))
{
writer.BaseStream.SetLength(0);
transferSpeedTimer.Elapsed += ((sender, e) =>
{
transferSpeed = writer.BaseStream.Position - bytesPreviousPosition;
bytesPreviousPosition = writer.BaseStream.Position;
});
transferSpeedTimer.Start();
while (moreChunks)
{
FileMessage message = hostChannel.ReceiveFile();
moreChunks = message.FileMetaData.MoreChunks;
writer.BaseStream.Position = filePosition;
// This is golden, but I need to extrapolate it out and do the stream copy myself so I can do calculations on a per byte basis.
message.ChunkData.CopyTo(writer.BaseStream);
filePosition += message.FileMetaData.ChunkLength;
// TODO This needs to be more granular
TotalBytesTransferred += message.FileMetaData.ChunkLength;
}
OnTransferComplete(this, EventArgs.Empty);
}
}
else
{
transferSpeedTimer.Elapsed += ((sender, e) =>
{
totalElapsedSeconds += (int)transferSpeedTimer.Interval;
transferSpeed = TotalBytesTransferred / totalElapsedSeconds;
});
transferSpeedTimer.Start();
host.Open();
}
问题是,将文件流式传输到客户端需要一段时间,因此我得到的速度是错误的。这里我的目标是对TotalByTestTransferred
变量进行更精细的修改,以便更好地表示在任何给定时间向客户端发送的数据量
现在,对于客户端代码,它使用了一种完全不同的计算传输速度的方法
if (Role == FileTransferItem.FileTransferRole.Receiver)
{
hostChannel = channelFactory.CreateChannel();
((IContextChannel)hostChannel).OperationTimeout = new TimeSpan(3, 0, 0);
bool moreChunks = true;
long bytesPreviousPosition = 0;
using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(fileWritePath)))
{
writer.BaseStream.SetLength(0);
transferSpeedTimer.Elapsed += ((sender, e) =>
{
transferSpeed = writer.BaseStream.Position - bytesPreviousPosition;
bytesPreviousPosition = writer.BaseStream.Position;
});
transferSpeedTimer.Start();
while (moreChunks)
{
FileMessage message = hostChannel.ReceiveFile();
moreChunks = message.FileMetaData.MoreChunks;
writer.BaseStream.Position = filePosition;
// This is golden, but I need to extrapolate it out and do the stream copy myself so I can do calculations on a per byte basis.
message.ChunkData.CopyTo(writer.BaseStream);
filePosition += message.FileMetaData.ChunkLength;
// TODO This needs to be more granular
TotalBytesTransferred += message.FileMetaData.ChunkLength;
}
OnTransferComplete(this, EventArgs.Empty);
}
}
else
{
transferSpeedTimer.Elapsed += ((sender, e) =>
{
totalElapsedSeconds += (int)transferSpeedTimer.Interval;
transferSpeed = TotalBytesTransferred / totalElapsedSeconds;
});
transferSpeedTimer.Start();
host.Open();
}
这里,我的TotalByTestTransferd
也基于传入的块的长度。我知道如果我自己编写流,而不是对流使用CopyTo
,我可以得到更精细的计算,但我不确定如何最好地进行这项工作
有人能帮我吗?在这个类之外,我有另一个类轮询TransferSpeed
的属性,因为它是在内部更新的
我很抱歉,如果我发布了太多的代码,但我不知道该发布什么和不该发布什么
EDIT:我意识到,至少在服务器端实现中,我可以通过读取流的返回消息值的位置来更精确地读取已传输的字节数。然而,我不知道如何做到这一点,以确保我的计数绝对完整。我想在传输流时使用计时器并轮询位置,但随后可能会发出下一个呼叫,我很快就会失去同步
如何轮询返回流中的数据,并立即知道流何时结束,以便快速将流中剩余的数据添加到字节计数中?好的,我找到了最适合我的方法。我不知道它是否完美,但它非常适合我的需要 在
服务器
端,我们有这段代码来传输文件。chunkedFile
类显然执行分块,但这是将信息发送到客户机的代码
public FileMessage ReceiveFile()
{
byte[] buffer = chunkedFile.NextChunk();
FileMessage message = new FileMessage();
message.FileMetaData = new FileMetaData(chunkedFile.MoreChunks, buffer.LongLength, chunkedFile.CurrentPosition);
message.ChunkData = new MemoryStream(buffer);
TotalBytesTransferred = chunkedFile.CurrentPosition;
UpdateTotalBytesTransferred(message);
if (!chunkedFile.MoreChunks)
{
OnTransferComplete(this, EventArgs.Empty);
Timer timer = new Timer(20000f);
timer.Elapsed += (sender, e) =>
{
StopSession();
timer.Stop();
};
timer.Start();
}
return message;
}
客户端基本上调用此代码,服务器继续获取新的块,将其放入流中,根据chunkedFile
的位置更新TotalByTestTransferred
(它跟踪用于从中提取数据的底层文件系统文件)。稍后我将展示方法updateTotalByTestTransferred(message)
,因为服务器和客户端的所有代码都驻留在该方法中,以实现对TotalByTestTransferred
的更细粒度轮询
接下来是客户端工作
public FileMessage ReceiveFile()
{
if (!transferSpeedTimer.Enabled)
transferSpeedTimer.Start();
byte[] buffer = chunkedFile.NextChunk();
FileMessage message = new FileMessage();
message.FileMetaData = new FileMetaData(chunkedFile.MoreChunks, buffer.LongLength);
message.ChunkData = new MemoryStream(buffer);
if (!chunkedFile.MoreChunks)
{
OnTransferComplete(this, EventArgs.Empty);
Timer timer = new Timer(20000f);
timer.Elapsed += (sender, e) =>
{
StopSession();
timer.Stop();
};
timer.Start();
}
//This needs to be more granular. This method is called too infrequently for a fast and accurate enough progress of the file transfer to be determined.
TotalBytesTransferred += buffer.LongLength;
return message;
}
hostChannel = channelFactory.CreateChannel();
((IContextChannel)hostChannel).OperationTimeout = new TimeSpan(3, 0, 0);
bool moreChunks = true;
using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(fileWritePath)))
{
writer.BaseStream.SetLength(0);
while (moreChunks)
{
FileMessage message = hostChannel.ReceiveFile();
moreChunks = message.FileMetaData.MoreChunks;
UpdateTotalBytesTransferred(message);
writer.BaseStream.Position = filePosition;
message.ChunkData.CopyTo(writer.BaseStream);
TotalBytesTransferred = message.FileMetaData.FilePosition;
filePosition += message.FileMetaData.ChunkLength;
}
OnTransferComplete(this, EventArgs.Empty);
}
这段代码非常简单。它调用主机以获取文件流,并使用updateTotalByTestTransferred(message)
方法。它需要做一些工作来记住正在写入的底层文件的位置,并将流复制到该文件中,同时在完成后更新totalbytesttransfered
我实现所需粒度的方法是使用updateTotalByTestTransferred
方法,如下所示。它对服务器和客户端的工作原理完全相同
private void UpdateTotalBytesTransferred(FileMessage message)
{
long previousStreamPosition = 0;
long totalBytesTransferredShouldBe = TotalBytesTransferred + message.FileMetaData.ChunkLength;
Timer timer = new Timer(500f);
timer.Elapsed += (sender, e) =>
{
if (TotalBytesTransferred + (message.ChunkData.Position - previousStreamPosition) < totalBytesTransferredShouldBe)
{
TotalBytesTransferred += message.ChunkData.Position - previousStreamPosition;
previousStreamPosition = message.ChunkData.Position;
}
else
{
timer.Stop();
timer.Dispose();
}
};
timer.Start();
}
private void updateTotalByTestTransferred(FileMessage)
{
长前一个流位置=0;
long TotalByTestTransferred应为=TotalByTestTransferred+message.FileMetaData.ChunkLength;
定时器=新定时器(500f);
计时器运行时间+=(发送方,e)=>
{
if(TotalByTestTransferred+(message.ChunkData.Position-previousStreamPosition)
这样做的目的是接收FileMessage,它基本上只是一个流和一些关于文件本身的信息。它有一个变量previousStreamPosition
,用来记住它在轮询底层流时的最后一个位置。它还根据已传输的字节数加上流的总长度,使用TotalByTestTransferredShouldBe
进行简单计算
最后,创建并执行一个计时器,它会在每次勾选时检查是否需要增加TotalByTestTransfered
。如果它不应该再更新它(基本上到达流的末尾),它将停止