Delphi7 - 我如何复制正在被写入的文件?

3
我有一个应用程序在主机上每秒钟记录信息到一份日志文本文件中。使用同一应用程序的网络上的从机PC想要将此文本文件复制到其本地驱动器。我可以看出会存在文件访问问题。
这些文件应该不超过30-40MB大小。网络将是100MB以太网。我可以看出复制过程可能需要超过1秒的时间,这意味着记录PC将需要在读取文件时打开文件进行写入。
对于文件编写(记录)和文件复制过程,最佳方法是什么?我知道有标准的Windows CopyFile()过程,但是这给了我文件访问问题。也有使用fmShareDenyNone标志的TFileStream,但这也偶尔会给我访问问题(大约每周1次)。
实现此任务的最佳方法是什么?
我的当前文件日志记录:
procedure FSWriteline(Filename,Header,s : String);
var LogFile : TFileStream;
line : String;
begin
     if not FileExists(filename) then
     begin
          LogFile := TFileStream.Create(FileName, fmCreate or fmShareDenyNone);
          try
             LogFile.Seek(0,soFromEnd);
             line := Header + #13#10;
             LogFile.Write(line[1],Length(line));
             line := s + #13#10;
             LogFile.Write(line[1],Length(line));
          finally
                 logfile.Free;
          end;
     end else begin
         line := s + #13#10;
         Logfile:=tfilestream.Create(Filename,fmOpenWrite or fmShareDenyNone);
         try
            logfile.Seek(0,soFromEnd);
            Logfile.Write(line[1], length(line));
         finally
            Logfile.free;
         end;
     end;
end;

我的文件复制程序:

procedure DoCopy(infile, Outfile : String);
begin
     ForceDirectories(ExtractFilePath(outfile)); //ensure folder exists
     if FileAge(inFile) = FileAge(OutFile) then Exit; //they are the same modified time
     try
        { Open existing destination }
        fo := TFileStream.Create(Outfile, fmOpenReadWrite or fmShareDenyNone);
        fo.Position := 0;
     except
           { otherwise Create destination }
           fo := TFileStream.Create(OutFile, fmCreate or fmShareDenyNone);
     end;
     try
        { open source }
        fi := TFileStream.Create(InFile, fmOpenRead or fmShareDenyNone);
        try
           cnt:= 0;
           fi.Position := cnt;
           max := fi.Size;
           {start copying }
           Repeat
                 dod := BLOCKSIZE; // Block size
                 if cnt+dod>max then dod := max-cnt;
                 if dod>0 then did := fo.CopyFrom(fi, dod);
                 cnt:=cnt+did;
                 Percent := Round(Cnt/Max*100);
           until (dod=0)
        finally
               fi.free;
        end;
     finally
            fo.free;
     end;
end;
3个回答

2
我建议一开始不要反复关闭和重新打开共享文件。由于你每秒钟写入一次,所以这只是不必要的开销。
在主端,创建并关闭文件(fmCreate标志不能与其他标志一起使用!),然后以fmOpenWrite模式和fmShareDenyWrite共享重新打开它,在需要时保持打开状态并写入数据。
在从属端,使用fmOpenRead模式和fmShareDenyNone共享打开文件,在每秒钟读取一次。无需每次将整个共享文件复制到网络上,这是浪费带宽的。只需读取过去几秒钟内编写的任何新数据即可。如果从属端需要将数据存储在本地文件中,则可以独立管理一个单独的本地文件,并在需要时将新数据推送到该本地文件中。

好建议,关于日志记录机制 - 我一定会实施。在我的情况下,从属方不需要每秒钟复制文件,而是一天复制一次 - 所以它会在复制之前检查两个文件的修改时间。 - Simon
re fmCreate:这是TFileStream类实现的一个缺点。Windows API允许在没有这种解决方法的情况下使用标志创建文件。我编写了自己的类来封装它:http://svn.berlios.de/svnroot/repos/dzchart/utilities/dzLib/trunk/src/u_dzFileStreams.pas - dummzeuch
限制在后来的VCL版本中被移除。新增了参数到FileCreate()和TFileStream的构造函数,允许同时指定访问权限和共享权限。 - Remy Lebeau

1

针对您的特定偶发性问题:

您没有说明使用的 Delphi 版本。

TFileStream.Create() 构造函数存在一个 bug,包括至少 2007 版本在内。这可能解释了您偶发的并发问题。

话虽如此,我认为该 bug 更可能导致文件未按预期创建(当另外指定 ShareMode 时),但这可能反过来导致您的并发问题。

解决方法之一是,在需要创建文件时,首先创建文件,然后作为单独的构造函数调用打开它进行写入 - 这实际上将文件创建作为一个单独的步骤,文件写入成为该过程的一致部分:

  if not FileExists(filename) then
  begin
    // There may be a more efficient way of creating an empty file, but this 
    //  illustrates the approach

    LogFile := TFileStream.Create(FileName, fmCreate);
    LogFile.Free;

    line := Header + #13#10 + s + #13#10;
  end
  else
    line := s + #13#10;

  Logfile:=tfilestream.Create(Filename,fmOpenWrite or fmShareDenyNone);
  try
    logfile.Seek(0,soFromEnd);
    Logfile.Write(line[1], length(line));
  finally
    Logfile.free;
  end;

使用Delphi7(标题中已经说明了:))。我会查看这个创建错误的问题。谢谢! - Simon
啊,是的 - 我怎么没注意到呢?LOL - Deltics

0

使用标准的Append文件创建/打开命令,使用write更新日志,并立即close文件。

在操作系统上使用作业来复制/移动文件;让它重试并以大于所需频率启动。

如果您想从Delphi中执行此操作,则使用MoveFile移动整个内容。

您可能希望将日志写入和移动都包装在try-except中,以便在文件系统(Windows上的NTFS?)无法为您解决并发性时可以重试合理次数。在最坏的情况下,要么:

  1. 文件已移动并被重新创建和写入。
  2. 文件没有立即移动,因为正在写入。

如果操作系统无法解决竞争条件,则必须使用信号量/锁来优先处理饥饿的操作。


此答案包含多条错误建议。 - David Heffernan
@david - 同意。我尝试使用TFile,但很快就遇到了问题。 - Simon

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接