好的,所以我正在处理我的文件传输服务,我可以使用WCF流传输文件。我获得了很好的速度,我最终能够获得良好的简历支持,因为我在流式传输之前将文件分成小块。
然而,在测量详细的传输速度时,我遇到了服务器端传输和客户端接收问题,因为消息是流式传输和写入的。
以下是文件分块的代码,每次需要向客户端发送另一个分块时,服务都会调用该代码。
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;
}
在客户端在WCF调用中调用的此方法中,我获取下一个块的信息,创建我的消息,在传输完成后使用计时器执行一些操作以停止会话并更新传输速度。在我返回消息之前不久,我使用缓冲区的长度递增TotalBytesTransferred
,用于帮助我计算传输速度。
问题在于,将文件流式传输到客户端需要一段时间,因此我得到的速度是错误的。我在此尝试的目标是对TotalBytesTransferred
变量进行更细粒度的修改,以便更好地表示在任何给定时间向客户端发送的数据量。
现在,对于客户端代码,它使用完全不同的方式计算传输速度。
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();
}
在这里,我的TotalBytesTransferred
也是基于进入的块的长度。我知道如果我自己编写流而不是使用CopyTo
,我可以得到更精细的计算流,但我不确定如何最好地解决这个问题。
有人可以帮我吗?在本课程之外,我有另一个课程,在TransferSpeed
内部更新内部更新属性。
如果我发布了太多代码,我很抱歉,但我不确定要发布什么内容以及什么不发布。
编辑:我至少认识到服务器端实现,我可以通过读取返回消息值的位置来更详细地读取已传输的字节数。流。但是,我不知道如何做到这一点,以确保我的计数绝对完整。我想可能使用计时器并在传输流时轮询位置,但随后可能会进行下一次调用,我很快就会失去同步。
如何从返回的流中轮询数据并立即知道流何时完成,以便我可以快速将剩余的流剩余部分加到我的字节数中?
答案 0 :(得分:0)
好的,我找到了对我来说最理想的东西。我不知道它是否完美,但它对我的需求非常好。
在Server
方面,我们有这个代码来完成传输文件的工作。 chunkedFile
类显然会进行分块,但这是将信息发送到Client
的代码。
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;
}
客户端基本上调用此代码,服务器继续获取新块,将其放入流中,根据TotalBytesTransferred
(跟踪chunkedFile
的位置更新UpdateTotalBytesTransferred(message)
用于从中绘制数据的基础文件系统文件。我将在稍后展示方法TotalBytesTransferred
,因为这是服务器和客户端所有代码驻留的位置,以实现 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);
}
的更细粒度的轮询。
接下来是客户端工作。
UpdateTotalBytesTransferred(message)
这段代码非常简单。它调用主机来获取文件流,并使用TotalBytesTransferred
方法。它有一点工作要记住正在编写的底层文件的位置,并将流复制到该文件,同时在完成后更新UpdateTotalBytesTransferred
。
我实现我所寻求的粒度的方式是使用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();
}
方法,如下所示。它对服务器和客户端的工作方式完全相同。
previousStreamPosition
这样做的内容是FileMessage,它基本上只是一个流和一些关于文件本身的信息。它有一个变量totalBytesTransferredShouldBe
来记住它在轮询底层流时的最后位置。它还根据已传输的字节数加上流的总长度,使用TotalBytesTransferred
进行简单计算。
最后,创建并执行一个计时器,在每次勾选检查时查看是否需要递增{{1}}。如果它不应该再更新它(基本上到达流的末尾),它会停止并处理计时器。
这一切都让我可以获得有关传输了多少字节的非常小的读数,这使我能够以更流畅的方式更好地计算总进度,从而更准确地测量所实现的文件传输速度。