Delphi 保证一段信息一次存储在一个地方

Delphi 保证一段信息一次存储在一个地方,delphi,Delphi,在应用程序关闭时,我想通过Indy的Internet向服务器发送一个文件。如果数据发送正常,我想从计算机上删除该文件。关键的一点是,我的文件不应该同时存储在服务器和本地计算机的两个位置 例如,我的应用程序可能会在将文件发送到服务器之后以及从本地磁盘删除文件之前意外停止。在这种情况下,文件将同时存在于两个位置 应用程序可能会因为以下原因停止:电源故障、Control+Alt+Del、操作系统关机、用户关机、系统挂起可能还有其他原因我忘记了 如何保证文件存储在一个地方 我想我们可以考虑把文件写到磁盘

在应用程序关闭时,我想通过Indy的Internet向服务器发送一个文件。如果数据发送正常,我想从计算机上删除该文件。关键的一点是,我的文件不应该同时存储在服务器和本地计算机的两个位置

例如,我的应用程序可能会在将文件发送到服务器之后以及从本地磁盘删除文件之前意外停止。在这种情况下,文件将同时存在于两个位置

应用程序可能会因为以下原因停止:电源故障、Control+Alt+Del、操作系统关机、用户关机、系统挂起可能还有其他原因我忘记了

如何保证文件存储在一个地方


我想我们可以考虑把文件写到磁盘是即时的,因为文件很小。p> 不,没有保证。操作系统和控制操作系统的最终用户总是可以终止进程。然而,操作系统通常不会尝试这样的事情,用户当然不会在不知不觉中杀死你的应用程序

您可以在关机期间运行任何您喜欢的代码,例如,在Form1Close中。如果运行一些代码而不需要处理几秒钟的消息,Windows可能会认为您的应用程序已被冻结,并询问用户是否希望杀死该应用程序。因此,和往常一样,您应该在自己的线程中执行慢代码,以便主应用程序线程仍然能够响应。[但如果最终用户不要求Windows终止该进程,它可以无限期地运行,即使它表现不好。]

此外,如果您希望此代码真正运行几秒钟,那么最好告诉最终用户发生了什么。例如,可以显示一个窗口

如果我是你,我会以主窗体编写OnCloseQuery处理程序。这个函数设置CanClose:=false,显示状态窗口,并启动关闭线程。当此线程完成时,它将关闭主窗体,例如通过向其发送消息。OnCloseQuery处理程序还应检查关闭线程是否正在运行。如果是这样,它应该简单地设置CanClose:=false,但不启动另一个关闭线程。例如,如果最终用户反复单击关闭框,就会发生这种情况。我认为OnCloseQuery过程将在线程启动关闭主窗体时运行。这一次应该正常关闭。您可以通过编写一些代码告诉OnCloseQuery处理程序它是由线程启动的,或者在线程完成时设置ShutdownThreadComplete:=true标志来实现这一点。因此,您将执行CanClose:=shutdownthreadplete

大概是这样的:

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if not ShutdownThreadStarted then
  begin
    CreatePleaseWaitMessage;
    StartShutdownThread;
    ShutdownThreadStated := true;
    CanClose := false;
    Exit;
  end;

  CanClose := ShutdownThreadComplete;
end;

不,不能保证。操作系统和控制操作系统的最终用户总是可以终止进程。然而,操作系统通常不会尝试这样的事情,用户当然不会在不知不觉中杀死你的应用程序

您可以在关机期间运行任何您喜欢的代码,例如,在Form1Close中。如果运行一些代码而不需要处理几秒钟的消息,Windows可能会认为您的应用程序已被冻结,并询问用户是否希望杀死该应用程序。因此,和往常一样,您应该在自己的线程中执行慢代码,以便主应用程序线程仍然能够响应。[但如果最终用户不要求Windows终止该进程,它可以无限期地运行,即使它表现不好。]

此外,如果您希望此代码真正运行几秒钟,那么最好告诉最终用户发生了什么。例如,可以显示一个窗口

如果我是你,我会以主窗体编写OnCloseQuery处理程序。这个函数设置CanClose:=false,显示状态窗口,并启动关闭线程。当此线程完成时,它将关闭主窗体,例如通过向其发送消息。OnCloseQuery处理程序还应检查关闭线程是否正在运行。如果是这样,它应该简单地设置CanClose:=false,但不启动另一个关闭线程。例如,如果最终用户反复单击关闭框,就会发生这种情况。我认为OnCloseQuery过程将在线程启动关闭主窗体时运行。这一次应该正常关闭。您可以通过编写一些代码告诉OnCloseQuery处理程序它是由线程启动的,或者在线程完成时设置ShutdownThreadComplete:=true标志来实现这一点。因此,您将执行CanClose:=shutdownthreadplete

大概是这样的:

procedure TForm1.FormCloseQuery(Sender: TObject; var CanClose: Boolean);
begin
  if not ShutdownThreadStarted then
  begin
    CreatePleaseWaitMessage;
    StartShutdownThread;
    ShutdownThreadStated := true;
    CanClose := false;
    Exit;
  end;

  CanClose := ShutdownThreadComplete;
end;

您可以使用以下Windows API调用来确保数据写入磁盘:

var 
  F: TFileStream;

begin
  F := TFileStream.Create(....
  try
    F.Write(...)
  (...)
    FlushFileBuffers(F.Handle); // this will flush the content to disk
  finally
    F.Free;
  end;
end;

补充说明:

在将文件发送到磁盘之前,决不能删除文件内容。ACID行为的一个公理是在新数据可用之前保留旧数据。你也一样

在 e可能性将是一个背景过程。您可以向作为轻型Windows服务运行的后台进程发送消息,例如,然后退出客户端应用程序。后台进程将负责将文件发送到服务器,并从服务器接收到重复单元确认。然后后台进程将能够删除该文件,并等待下一个进程


客户端只需确保后台进程已经处理了请求。因为它是本地的,所以它是即时的

您可以使用以下Windows API调用来确保数据写入磁盘:

var 
  F: TFileStream;

begin
  F := TFileStream.Create(....
  try
    F.Write(...)
  (...)
    FlushFileBuffers(F.Handle); // this will flush the content to disk
  finally
    F.Free;
  end;
end;

补充说明:

在将文件发送到磁盘之前,决不能删除文件内容。ACID行为的一个公理是在新数据可用之前保留旧数据。你也一样


一种可能是背景程序。您可以向作为轻型Windows服务运行的后台进程发送消息,例如,然后退出客户端应用程序。后台进程将负责将文件发送到服务器,并从服务器接收到重复单元确认。然后后台进程将能够删除该文件,并等待下一个进程


客户端只需确保后台进程已经处理了请求。因为它是本地的,所以它是即时的

否,如果在主窗体的OnClose或OnDestroy中执行以下操作: 1/写入文件 2/关闭文件 文件将完全写入并关闭。
有关更多说明,请参阅closefile帮助。

否,如果在主窗体的OnClose或OnDestroy中执行以下操作: 1/写入文件 2/关闭文件 文件将完全写入并关闭。
查看closefile帮助以了解更多解释。

我不确定您为什么要求不能将文件同时保存在客户端和服务器上。但是,如果您可以稍微放宽该限制,您可以在读取后重命名该文件,以指示正在发送该文件,然后在确认服务器已收到该文件后将其最终删除

如果这是不可接受的,您可以使用以下稍微复杂一些的步骤:

通知服务器您将发送文件,并取回加密密钥 从磁盘读取文件 加密文件并将加密版本写入磁盘,确保已刷新 打开未加密的文件并用0覆盖其内容,确保已刷新 从现在开始删除未加密的文件,因为该文件实际上不再存在于客户端的磁盘上 将加密文件发送到服务器 删除加密文件
如果在发送过程中出现错误,应用程序下次启动时将找到加密文件,然后可以将加密文件发送到服务器。服务器已经知道最初生成的解密密钥。

我不确定您为什么要求不能同时将文件保存在客户端和服务器上。但是,如果您可以稍微放宽该限制,您可以在读取后重命名该文件,以指示正在发送该文件,然后在确认服务器已收到该文件后将其最终删除

如果这是不可接受的,您可以使用以下稍微复杂一些的步骤:

通知服务器您将发送文件,并取回加密密钥 从磁盘读取文件 加密文件并将加密版本写入磁盘,确保已刷新 打开未加密的文件并用0覆盖其内容,确保已刷新 从现在开始删除未加密的文件,因为该文件实际上不再存在于客户端的磁盘上 将加密文件发送到服务器 删除加密文件
如果在发送过程中出现错误,应用程序下次启动时将找到加密文件,然后可以将加密文件发送到服务器。服务器已经知道最初生成的解密密钥。

这就是我部分解决问题的方法

 Delete the file from disk (keep copy in RAM);
 Send the file to server;
 if server receives file ok
 then DoNothing (file was already deleted)
 else Write file back to disk;

问题是在第1步和第2步之间,文件不存在。如果应用程序在该点死亡,文件将丢失,但是,这比在两个位置更容易接受。不幸的是,步骤1和步骤2之间的延迟非常大,因为与远程服务器的通信速度非常慢,甚至可能长达1-2秒。

这就是我部分解决问题的方法

 Delete the file from disk (keep copy in RAM);
 Send the file to server;
 if server receives file ok
 then DoNothing (file was already deleted)
 else Write file back to disk;

问题是在第1步和第2步之间,文件不存在。如果应用程序在该点死亡,文件将丢失,但是,这比在两个位置更容易接受。不幸的是,第1步和第2步之间的延迟非常大,因为与远程服务器的通信速度非常慢,甚至可能长达1-2秒。

阅读您的文章,我看到了第二个解决方案,它不涉及锁定GUI:我可以设置Indy,希望如果连接不成功,可以中止连接

在x秒后进行d。这样Windows将永远不会看到应用程序被冻结。感谢Andreas+1。TIdTCPClient在Indy 9及更早版本的Connect方法中有一个ATimeout参数,在Indy 10中有一个ConnectTimeout属性。感谢Remy。这正是我所需要的。阅读你的帖子,我看到了第二个解决方案,它不涉及锁定GUI:我可以设置Indy,希望如果x秒后连接不成功,它可以中止连接。这样Windows将永远不会看到应用程序被冻结。感谢Andreas+1。TIdTCPClient在Indy 9及更早版本的Connect方法中有一个ATimeout参数,在Indy 10中有一个ConnectTimeout属性。感谢Remy。这正是我所需要的。你为什么不假设它随时都可能消亡,而是担心确保保存到磁盘上的数据的一致性在设计上得到保护。这就是所谓交易的意思。要么全部存储,要么全部不存储。您可能有磁盘错误、电源问题或网络问题。你可能有很多问题。您无法阻止它们,您只能尝试使您的设计健壮并明确地处理它们。@Warren请参见下面我的答案:您可以轻松地强制将文件缓冲区刷新到磁盘中,以强制ACID行为,即使断电。您好Warren。你能详细说明一下吗,或者给我指一些具体的文档?谢谢。我在下面更新了我的答案。你为什么不假设它随时都可能消亡,而是担心确保保存到磁盘上的数据的一致性在设计上得到了保护。这就是所谓交易的意思。要么全部存储,要么全部不存储。您可能有磁盘错误、电源问题或网络问题。你可能有很多问题。您无法阻止它们,您只能尝试使您的设计健壮并明确地处理它们。@Warren请参见下面我的答案:您可以轻松地强制将文件缓冲区刷新到磁盘中,以强制ACID行为,即使断电。您好Warren。你能详细说明一下吗,或者给我指一些具体的文档?谢谢。我已经更新了下面的答案。嗨。问题不是将文件写入磁盘这需要毫秒,而是与服务器通信需要1-2秒。我认为释放流也会刷新数据。@否,TFileStream.Destroy将关闭文件句柄,该句柄不会将数据刷新到磁盘。它将数据刷新到内部Windows缓冲区,稍后将物理刷新到磁盘。请参阅我的答案中MSDN的链接。一种可能是后台进程------您刚刚将问题从应用程序转移到后台进程服务。如果在服务处于第一步删除本地文件和第二步向服务器发送文件之间时计算机/电源出现故障,该怎么办?ACID行为的一个公理是…-——我不反对ACID,但由于计算机在这两次操作之间可能会出现故障,我宁愿不把文件放在任何地方,也不愿把它放在两个地方,因为它更适合我的需要。问题不是将文件写入磁盘这需要毫秒,而是与服务器通信需要1-2秒。我认为释放流也会刷新数据。@否,TFileStream.Destroy将关闭文件句柄,该句柄不会将数据刷新到磁盘。它将数据刷新到内部Windows缓冲区,稍后将物理刷新到磁盘。请参阅我的答案中MSDN的链接。一种可能是后台进程------您刚刚将问题从应用程序转移到后台进程服务。如果在服务处于第一步删除本地文件和第二步向服务器发送文件之间时计算机/电源出现故障,该怎么办?ACID行为的一个公理是…-——我不反对ACID,但由于计算机在这两次操作之间可能会出现故障,我宁愿不把文件放在任何地方,也不愿把它放在两个地方,因为它更适合我的需要。嗨,Thorsten。好主意。也许一开始我可以用一个重命名的文件实现简化版本。但是有一个问题,当我将文件发送到服务器时,我必须等待服务器响应成功接收文件。我收到了文件,但从来没有机会回答,我将再次在两个地方的文件。这是加密阻止。虽然在服务器接收到加密文件后,如果通信恰好在正确的时刻中断,或者更确切地说,在错误的时刻中断,则该加密文件仍可能存在于客户端,但客户端上的文件将使用只有服务器具有的密钥进行加密。因此,该文件也可能不再存在于客户端。嗨,Thorsten。好主意。也许一开始我可以用一个重命名的文件实现简化版本。但是有一个问题,当我将文件发送到服务器时,我必须等待服务器响应成功接收文件。我收到了文件,但从来没有机会回复,我会在两个地方再次收到文件,即 加密所阻止的。虽然在服务器接收到加密文件后,如果通信恰好在正确的时刻中断,或者更确切地说,在错误的时刻中断,则该加密文件仍可能存在于客户端,但客户端上的文件将使用只有服务器具有的密钥进行加密。因此,该文件也可能不再存在于客户端。