12

这是我关于以块下载文件的问题的延续。解释会很大,所以我会尝试将它分成几个部分。

1)我试图做什么?

我正在为 Window-Phone 应用程序创建下载管理器。首先,我尝试解决下载大文件的问题(解释在上一个问题中)。不,我想添加“可恢复下载”功能。

2)我已经做了什么。

目前我有一个运行良好的下载管理器,它允许绕过 Windows Phone RAM 限制。这个管理器的情节是,它允许使用 HTTP Range 标头下载小块文件。

快速解释它的工作原理:

该文件以恒定大小的块下载。我们称这个大小为“delta”。文件块下载后,以追加模式保存到本地存储(硬盘,在 WP 上称为独立存储)(因此,下载的字节数组始终添加到文件末尾)。下载单个块后,语句

if (mediaFileLength >= delta) // mediaFileLength is a length of downloaded chunk

被检查。如果它是真的,那意味着,还有一些东西要下载,并且这个方法被递归调用。否则,这意味着这个块是最后一个,并且没有任何东西可以下载。

3)有什么问题?

直到我在一次性下载中使用了这个逻辑(我的意思是一次性,当你开始下载文件并等到下载完成时)效果很好。但是,我决定,我需要“恢复下载” 功能。所以,事实:

3.1)我知道,文件块大小是一个常数。

3.2)我知道,文件何时完全下载。(这是我的应用程序逻辑的间接结果,不会让你厌烦解释,只是假设这是事实)

在这两个陈述的假设下,我可以证明,下载的块数等于 (CurrentFileLength)/delta。其中CurrentFileLenght是已下载文件的大小(以字节为单位)。

要恢复下载文件,我应该简单地设置所需的标题并调用下载方法。这似乎是逻辑,不是吗?我试图实现它:

    // Check file size
    using (IsolatedStorageFileStream fileStream = isolatedStorageFile.OpenFile("SomewhereInTheIsolatedStorage", FileMode.Open, FileAccess.Read))
    {
      int currentFileSize = Convert.ToInt32(fileStream.Length);
      int currentFileChunkIterator = currentFileSize / delta;
    }

我看到的结果是什么?下载的文件长度等于2432000字节(增量为 304160,总文件大小约为4.5 MB,我们只下载了一半)。所以结果大约是7,995。(它实际上是 long/int 类型,所以它是 7,应该是 8!)为什么会这样?简单的数学告诉我们,文件长度应该是2433280,所以给定的值非常接近,但不相等。

进一步的调查表明,从 给出的所有值fileStream.Length都不准确,但都接近。

为什么会这样?我不确切知道,但也许 .Length 值是从文件元数据中获取的。也许,这种舍入对于这种方法是正常的。也许,当下载被中断时,文件并没有完全保存……(不,那真是太棒了,不可能)

所以问题就解决了——它是“如何确定下载的块数”。问题是如何解决它。

4)我对解决问题的想法。

我的第一个想法是在这里使用数学。设置一些epsilon-neiborhoodcurrentFileChunkIterator = currentFileSize / delta;并在语句中使用它。但这将要求我们记住类型 I 和类型 II 错误(或误报和错过,如果您不喜欢统计术语)。也许,没有什么可下载的了。另外,我没有检查提供的值和真​​实值的差异是否应该永久增长或者会有周期性波动。对于小尺寸(大约 4-5 MB),我只看到了增长,但这并不能证明什么。

所以,我在这里寻求帮助,因为我不喜欢我的解决方案。

5)我想听到的答案:

是什么导致了实际价值和收到的价值之间的差异?

有没有办法获得真正的价值?

如果不是,我的解决方案对这个问题有好处吗?

还有其他更好的解决方案吗?

PS 我不会设置 Windows-Phone 标签,因为我不确定这个问题是否与操作系统相关。我使用独立存储工具检查下载文件的大小,它显示的值与收到的值相同(对于屏幕截图中的俄语,我很抱歉):

文件大小错误图片

4

4 回答 4

0

你听说过关于一个菜鸟程序员和 10 个大师级程序员的轶事吗?Guru 程序员试图在他的解决方案中找到错误,noob 已经找到了它,但没有告诉它,因为它是愚蠢的,我们害怕被嘲笑。

为什么我想起了这个?因为情况类似。

我的问题的解释很重,我决定不提一些我确信工作正确的小方面。(而且他们确实工作正常)

其中一个小方面是,下载的文件是通过 AES PKCS7 填充加密的。好吧,解密工作正常,我知道,那我为什么要提呢?而我没有。

所以,然后我试图找出究竟是什么导致了最后一个块的错误。最可信的版本是关于缓冲的问题,我试图找到我在哪里留下丢失的字节。我一次又一次地测试,但我找不到它们,因为每一块都在保存而没有任何损失。有一天我领悟到:

没有勺子

没有错误。

AES PKCS7 有什么意义?嗯,主要是它使解密的文件更小。不多,只有 16 个字节。而且我的解密方法和下载方法都考虑到了,应该没有问题吧?

但是,当下载过程中断时会发生什么?最后一个块将正确保存,缓冲或其他不会有错误。然后我们要继续下载。下载的块数将等于currentFileChunkIterator = currentFileSize / delta;

在这里我应该问自己:“你为什么要做这么愚蠢的事情?”

“您下载的一个块大小不是 delta。实际上,它小于 delta ”。(解密使块更小到 16 个字节,记得吗?)

增量本身由 10 个相等的部分组成,它们正在被解密。所以我们不应该除以 delta,而是除以(delta - 16 * 10),即 (304160 - 160) = 304000

我感觉到这里有一只老鼠。让我们尝试找出下载的块的数量:

2432000 / 304000 = 8.等等... OH SHI~


所以,这就是故事的结局。

整个解决方案逻辑是正确的。

它失败的唯一原因是我认为,由于某种原因,下载的解密文件大小应该与下载的加密块的总和相同。

而且,当然,由于我没有提到解密(仅在上一个问题中提到,仅链接),所以你们谁也不能给我一个正确的答案。我对此感到非常抱歉。

于 2013-02-20T12:43:38.120 回答
0

我正在回答您的更新:

到目前为止,这是我的理解:实际写入文件的长度(四舍五入到下一个 1KiB)比您实际写入的长度要长。这会导致您对“file.Length == 下载量”的假设是错误的。

一种解决方案是单独跟踪此信息。创建一些元数据结构(可以使用相同的存储机制进行持久化)以准确跟踪已下载的块以及文件的整个大小:

[DataContract] //< I forgot how serialization on the phone works, please forgive me if the tags differ
struct Metadata
{
     [DataMember]
     public int Length;
     [DataMember]
     public int NumBlocksDownloaded;
}

假设您以连续的方式继续下载它们,这足以重建哪些块已下载,哪些尚未下载。

编辑

当然,在将数据写入流之前,您必须将代码从简单的追加更改为将流的位置移动到正确的块:

 file.Position = currentBlock * delta;
 file.Write(block, 0, block.Length);
于 2013-02-13T11:59:26.573 回答
0

就像一个可能的错误一样。不要忘记验证文件是否在请求期间被修改。特别是在两者之间的长时间内,可能会在暂停/恢复时发生。错误可能很大,例如文件被修改为小尺寸并且您的计数变得“错误”,并且文件大小相同但内容已修改,这将留下损坏的文件。

于 2013-02-18T19:19:52.197 回答
0

在继续我的评论..

我从您的描述中了解到的原始文件大小为 2432000 字节。
块大小设置为 304160 字节(或每个“增量”为 304160)。

因此,发送文件的机器能够填充 7 个块并发送它们。
接收机器现在有 7 x 304160 字节 = 2129120 字节。

最后一个块不会被填充到最后,因为没有足够的字节来填充它。所以它将包含:2432000 - 2129120 = 302880 小于 304160

如果添加数字,您将得到 7x304160 + 1x302880 = 2432000 字节因此根据原始文件完整传输到目的地。

问题是您正在计算 8x304160 = 2433280 坚持认为即使是最后一块也必须完全填充 - 但是用什么?为什么?

谦虚地说..您是否陷入某种数学混乱或我误解了您的问题?
请回答,原始文件大小是多少,另一端收到的大小是多少?(总数!)

于 2013-02-14T10:42:00.463 回答