如何在执行流操作时实现更好的粒度?

时间:2013-01-05 05:55:16

标签: c# wcf streaming bytebuffer

好的,所以我正在处理我的文件传输服务,我可以使用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内部更新内部更新属性。

如果我发布了太多代码,我很抱歉,但我不确定要发布什么内容以及什么不发布。

编辑:我至少认识到服务器端实现,我可以通过读取返回消息值的位置来更详细地读取已传输的字节数。流。但是,我不知道如何做到这一点,以确保我的计数绝对完整。我想可能使用计时器并在传输流时轮询位置,但随后可能会进行下一次调用,我很快就会失去同步。

如何从返回的流中轮询数据并立即知道流何时完成,以便我可以快速将剩余的流剩余部分加到我的字节数中?

1 个答案:

答案 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}}。如果它不应该再更新它(基本上到达流的末尾),它会停止并处理计时器。

这一切都让我可以获得有关传输了多少字节的非常小的读数,这使我能够以更流畅的方式更好地计算总进度,从而更准确地测量所实现的文件传输速度。