一些文件复制程序喜欢rsync
并且curl
能够恢复失败的传输/复制。
请注意,这些故障可能有很多原因,在某些情况下程序可以“清理”某些情况下程序不能。
当这些程序恢复时,它们似乎只是计算成功传输的文件/数据的大小,然后开始从源读取下一个字节并附加到文件片段上。
例如,“成功”到目的地的文件片段的大小是 1378 字节,因此它们只是从原始字节 1379 开始读取并添加到片段中。
我的问题是,知道字节是由位组成的,并且并非所有文件都将其数据分段为干净的字节大小的块,这些程序如何知道他们选择开始添加数据的点是正确的?
当写入目标文件时,会在程序、内核或文件系统级别发生类似于 SQL 数据库的某种缓冲或“事务”,以确保只有干净、格式良好的字节才能到达底层块设备?
或者程序是否假设最新的字节可能不完整,所以他们假设它坏了就删除它,重新复制字节并从那里开始追加?
知道并非所有数据都表示为字节,这些猜测似乎不正确。
当这些程序“恢复”时,他们如何知道他们从正确的地方开始?
为了清楚起见——真正的机制更复杂,以提供更好的安全性——你可以想象这样的写入磁盘操作:
如果进程在 (1) 处中断,则磁盘上没有任何内容,文件完好无损,并在前一个块处被截断。您发送了 5000 个字节,磁盘上只有 4096 个字节,您在偏移量 4096 处重新开始传输。
如果在 (2) 处,除了在内存中什么都不会发生。同(1)。如果在 (3) 处,数据已写入,但没有人记得它。您发送了 9000 个字节,写入了 4096 个字节,写入了 4096 个字节并丢失了,其余的只是丢失了。转移在偏移量 4096 处恢复。
如果在 (4) 处,则数据现在应该已提交到磁盘上。流中的下一个字节可能会丢失。您发送了 9000 个字节,写入了 8192 个字节,其余的丢失了,传输在偏移量 8192 处恢复。
这是一个简化的镜头。例如,阶段 3-4 中的每个“逻辑”写入都不是“原子的”,而是产生另一个序列(让我们将其编号为 #5),从而将块细分为适合目标设备(例如硬盘)的子块) 被发送到设备的主机控制器,主机控制器也有缓存机制,最后存储在磁碟上。这个子序列并不总是完全在系统的控制之下,因此将数据发送到硬盘并不能保证它已经被实际写入并且可以读取回来。
一些文件系统实现了日志,以确保最脆弱的点 (4)实际上不会受到攻击,通过将元数据写入,您猜对了,无论在阶段 (5) 发生什么,事务都将始终如一地工作。
如果系统在交易过程中被重置,它可以恢复到最近的完整检查点。写入的数据仍然丢失,与案例 (1) 相同,但恢复会处理这一点。实际上没有信息丢失。
注意:我没有查看
rsync
任何其他文件传输实用程序的来源。编写一个跳转文件末尾并以字节为单位获取该位置的位置的 C 程序是微不足道的。
这两个操作都是通过对标准 C 库函数的一次调用完成的
lseek()
(lseek(fd, 0, SEEK_END)
返回为文件描述符打开的文件的长度fd
,以字节为单位)。一旦对目标文件完成此操作,就
lseek()
可以对源文件进行类似的调用以跳转到适当的位置:lseek(fd, pos, SEEK_SET)
. 假设源文件的较早部分已被标识为未更改(不同的实用程序可能以不同的方式执行此操作),则传输可能会在该点继续。一个文件可能在磁盘上被分段,但文件系统将确保应用程序将文件视为一个连续的字节序列。
关于评论中关于位和字节的讨论:可以写入磁盘的最小数据单位是byte。单个字节需要在磁盘上分配至少一个数据块。块的大小取决于文件系统的类型,也可能取决于管理员在初始化文件系统时使用的参数,但通常在 512 字节和 4 KiB 之间。写入操作可能会被内核、底层 C 库或应用程序本身缓冲,并且作为优化,实际写入磁盘可能会以适当块大小的倍数发生。
不可能将单个位写入文件,如果写入操作失败,则不会在文件中留下“半写入字节”。
这基本上是两个问题,因为 curl 和 rsync 之类的程序非常不同。
对于像 curl 这样的 HTTP 客户端,它们会检查当前文件的大小,然后
Content-Range
在请求中发送标头。服务器要么使用状态码206
(部分内容)而不是200
(成功)继续发送文件范围,然后继续下载,要么忽略标头并从头开始,HTTP客户端别无选择,只能重新下载所有内容再次。此外,服务器可能会或可能不会发送
Content-Length
标头。您可能已经注意到某些下载没有显示百分比和文件大小。这些是服务器不告诉客户端长度的下载,因此客户端只知道它下载的数量,但不知道接下来会有多少字节。某些下载管理器使用
Content-Range
带有开始和停止位置的标头来一次从不同来源下载文件,如果每个镜像本身比您的网络连接慢,这会加快传输速度。另一方面,rsync 是用于增量文件传输的高级协议。它在服务器端和客户端生成文件部分的校验和,以检测哪些字节是相同的。然后它只发送差异。这意味着它不仅可以恢复下载,而且如果您在一个非常大的文件中间更改了几个字节而不重新下载文件,它甚至可以下载更改的字节。
用于恢复传输的另一个协议是 bittorrent,其中
.torrent
文件包含文件中块的校验和列表,因此可以以任意顺序从不同来源并行下载和验证块。请注意,rsync 和 bittorent 将验证您磁盘上的部分数据,而恢复 HTTP 下载则不会。因此,如果您怀疑部分数据已损坏,则需要检查完整性,即使用最终文件的校验和。但只是中断下载或失去网络连接通常不会损坏部分文件,而传输过程中的电源故障可能会这样做。
TL;DR:他们不能,除非他们使用的协议允许。
程序不能总是从任意位置恢复:例如,HTTP 请求只有在服务器支持并且客户端实现它时才能重新启动:这不是通用的,因此请查看程序的文档。如果服务器确实支持它,程序可以通过简单地作为协议的一部分询问来恢复传输。您通常会在下载目录中看到部分传输(它们通常标有“.partial”扩展名或类似名称。)
如果文件下载被暂停或以其他方式停止,客户端可以将文件写入磁盘并明确知道从哪里恢复。另一方面,如果客户端崩溃或写入文件时出错,则客户端必须假定文件已损坏并重新开始。BitTorrent通过将文件分解为“块”并跟踪哪些文件已成功下载,从而在一定程度上缓解了这一问题。它最多只能重做几块。Rsync做了类似的事情。
程序怎么知道内容是一样的?一种方法是验证客户端和服务器之间的某些标识符是否相同。这方面的一些例子是时间戳和大小,但有些机制可以特定于协议。如果标识符匹配,则客户端可以假设恢复将起作用。
如果你想要更明确的验证,HTTP 和朋友不应该是你的首选。您将希望使用一个协议,该协议还具有整个文件和每个传输块的校验和或哈希,以便您可以将下载的校验和与服务器的计算机校验和进行比较:任何不匹配的内容都将被重新下载。同样,BitTorrent 就是这种协议的一个例子。rsync 也可以选择执行此操作。
取决于用于传输的协议。但是 curl 使用 http 并且它按照它在文件中出现的顺序顺序传输数据。因此 curl 可以根据部分完成传输的文件大小恢复。事实上,您可以通过创建一个长度为 N(任何内容)的文件并要求它将该文件视为部分完成的下载(然后丢弃前 N 个字节)来欺骗它跳过前 N 个字节。