3

我有客户端/服务器应用程序,客户端应用程序将在其中打开文件。这些文件被分割成块,并发送到服务器。

客户端不仅发送文件块,还发送其他数据。每条消息(数据或文件块)都有一个优先级。

所有消息都会根据它们的优先级缓冲并写入 TCP 流。当前,只要文件完全发送或接收,输入和输出流就会在两侧(客户端/服务器)关闭。这意味着只要发送文件,流就会保持打开状态。

允许 TCP 连接失败,因为它将重新连接并恢复消息发送,因此,流将在某个时候关闭。

但是,例如,如果 JVM 会被杀死,那么流将不会被清理。

我解决这个问题的第一个想法是在终结器中添加清理代码,但我知道当 JVM 被杀死(或者如果 System.exit 被调用)时,这些代码可能不会运行。

我的第二个想法是重写应用程序的一部分,并且只使用流,只要它需要读/写一个块。因此,我最终会打开/关闭文件的次数与块的次数一样多。这种方法的优点是允许我使用 try-catch-finally 结构,但我有一种打开和关闭文件的直觉,这通常意味着相当多的开销。

那么当设计不允许finally{}块时,如何清理资源呢?还是应该避免这样的设计,也许以类似于我所描述的方式?

我还担心打开的文件数量可能与优先级一样多(理论上是无限的)。

大多数文件通常只有几 KiB,但在某些情况下,它们可能会达到几 GB。

提前感谢您的意见。

编辑:添加图像

文件传输原样

4

2 回答 2

1

看看java.lang.Runtime.addShutdownHook()。我会尝试添加一个钩子来关闭所有打开的流,你必须为这种情况维护的列表。但是,请注意:

在极少数情况下,虚拟机可能会中止,即在没有完全关闭的情况下停止运行。当虚拟机在外部终止时会发生这种情况,例如在 Unix 上使用 SIGKILL 信号或在 Microsoft Windows 上使用 TerminateProcess 调用。如果本地方法出错,例如破坏内部数据结构或尝试访问不存在的内存,虚拟机也可能中止。如果虚拟机中止,则无法保证是否会运行任何关闭挂钩。

于 2012-03-24T18:38:55.953 回答
1

如果我正确理解了这个问题,那么您担心在 JVM 以不受控制的方式终止的情况下无法正确清理。

虽然这是一个普遍问题,但在您的特定情况下,我认为没有问题需要担心。您只打开文件进行阅读,因此您的应用程序不会中断持久状态(据我了解,TCP 连接的另一端可以优雅地处理断开连接)。打开的文件是应用程序内部的一种状态。如果应用程序被杀死,操作系统会负责清理所有锁或它可能在内部用于处理对该文件的操作的任何数据结构。这意味着操作系统内核中不会留下任何“垃圾”,虽然它既不是一种优雅的清理方式,也不是推荐的清理方式,但它可以正常工作(当然,仅在紧急情况下使用它,而不是作为正常方式处理事情)。

当然,如果应用程序被杀死,它不会完成它正在执行的任何操作。这可能会导致不一致的应用程序级状态或某些应用程序逻辑的其他问题,但仍然不会损害任何操作系统级的架构(假设操作系统没有错误)。您可能会丢失数据或破坏应用程序的状态,但您不应该破坏内核中的文件系统或数据。

因此,如果您终止具有事务式操作的应用程序(您希望完全执行或根本不执行该操作,但中间状态不应该对外界可见),您可能会遇到问题。例如,如果您有一个文件并且需要将其替换为较新的版本。如果您首先截断旧文件,然后向其写入新内容(这是显而易见的方法),如果您的应用程序在截断文件之后但在写入新内容之前被终止,那么您就有麻烦了。然而,为了引发这样的风险,你需要可变状态,即写一些东西。如果你只看东西,你几乎肯定是安全的。

如果您确实遇到这种情况,您可以采取多种方式。一个是试图使应用程序防弹并确保它总是很好地清理。在实践中,这是非常困难的。您可以向 Java 应用程序添加关闭挂钩,该挂钩将在应用程序关闭时执行,但它仅适用于受控关闭(如killLinux 上的常规(SIGTERM))。但是,这并不能保护您免于应用程序被管理员强行杀死(kill -9在 Linux 上),由 OOM-killer(也是 Linux)等。当然,其他操作系统也有这些情况的等价物或应用程序以无法控制的方式关闭的其他情况。如果您没有编写在受控硬件和软件环境中运行的实时应用程序,那么几乎不可能阻止应用程序被强制终止并阻止其运行其清理程序的所有方式。

因此,合理的妥协通常是在应用程序中仅采取简单的预防措施(如关闭挂钩),但请记住,您不能阻止所有事情,因此使手动清理成为可能和容易。例如,覆盖文件的解决方案是将操作拆分为首先将旧文件移动到新名称以保留作为备份(此操作通常在操作系统级别是原子的,因此是安全的),然后编写将文件的新内容改成旧名称,然后检查新文件是否正确写入后,删除备份。这样,如果应用程序在操作之间被终止,则存在一个简单的清理过程:将备份文件移动到原始名称,从而恢复到较旧但一致的状态。这可以手动完成(但应记录在案),或者您可以向您的应用程序添加清理脚本或特殊的“清理”命令,以使此操作简单、可见,并消除在此过程中出现人为错误的可能性。假设您的应用程序很少被杀死,这通常比花费大量时间试图使应用程序防弹只是意识到它是不可能的更有效。拥抱失败通常比试图阻止它更好(不仅在编程中)。

您仍然可能在操作系统和硬件级别上被烧毁,但对于商品硬件和操作系统来说,这真的很难预防。即使您的应用程序在正确的位置看到新文件,它也可能没有实际写入磁盘,并且如果它仅驻留在缓存中,如果您的应用程序被杀死,它不会丢失,但如果有人拔掉电源,它会丢失插上机器。但处理这种失败是完全不同的故事。

长话短说,在您只读取文件的特定情况下,如果您的应用程序被杀死,操作系统应该执行所有清理,对于其他情况,上面提到了一些提示。

于 2012-03-24T19:32:07.500 回答