2

好的,我正在开发我的文件传输服务,我可以使用 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();
        }

在这里,myTotalBytesTransferred也是基于传入的块的长度。我知道如果我自己编写流而不是使用 for 流,我可以获得更精细的计算CopyTo,但我不确定如何最好地进行对这个。

有人可以帮我吗?在这个类之外,我有另一个类在TransferSpeed内部更新时轮询它的属性。

如果我发布了太多代码,我深表歉意,但我不确定该发布什么,不发布什么。

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

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

4

1 回答 1

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();
    }

这样做是接收 FileMessage,它基本上只是一个流和一些关于文件本身的信息。它有一个变量previousStreamPosition来记住它在轮询底层流时的最后位置。它还totalBytesTransferredShouldBe根据已传输的字节数加上流的总长度进行简单的计算。

最后,创建并执行一个计时器,它会在每次滴答时检查它是否需要增加TotalBytesTransferred. 如果它不应该再更新它(基本上到达流的末尾),它会停止并处理计时器。

这一切都使我可以非常少地读取已传输的字节数,这使我可以以更流畅的方式更好地计算总进度,因为可以更准确地测量所达到的文件传输速度。

于 2013-01-06T06:28:55.053 回答