简体   繁体   English

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

[英]Delphi7 - How can i copy a file that is being written to

I have an application that logs information to a daily text file every second on a master PC. 我有一个应用程序,每秒在主PC上将信息记录到每日文本文件。 A Slave PC on the network using the same application would like to copy this text file to its local drive. 使用相同应用程序的网络上的从属PC希望将此文本文件复制到其本地驱动器。 I can see there is going to be file access issues. 我可以看到存在文件访问问题。

These files should be no larger than 30-40MB each. 这些文件每个不应超过30-40MB。 the network will be 100MB ethernet. 网络将是100MB以太网。 I can see there is potential for the copying process to take longer than 1 second meaning the logging PC will need to open the file for writing while it is being read. 我可以看到复制过程有可能花费超过1秒的时间,这意味着日志记录PC需要在读取文件时打开文件进行写入。

What is the best method for the file writing(logging) and file copying procedures? 文件写入(日志记录)和文件复制过程的最佳方法是什么? I know there is the standard Windows CopyFile() procedure, however this has given me file access problems. 我知道有标准的Windows CopyFile()过程,但这给了我文件访问问题。 There is also TFileStream using the fmShareDenyNone flag, but this also very occasionally gives me an access problem too (like 1 per week). 还有TFileStream使用fmShareDenyNone标志,但这也偶尔会给我一个访问问题(比如每周1次)。

What is this the best way of accomplishing this task? 这是完成这项任务的最佳方式是什么?

My current File Logging: 我目前的文件记录:

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;

My file copy procedure: 我的文件复制程序:

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;

I would suggest not closing and reopening the shared file over and over to begin with. 我建议不要一遍又一遍地关闭和重新打开共享文件。 Since you write to it every second, that is just needless overhead. 既然你每秒都写到它,那就是不必要的开销。

On the Master side, create and close the file (the fmCreate flag cannot be used with other flags!), then re-open it in fmOpenWrite mode with fmShareDenyWrite sharing, leave it open, and write to it when needed. 在Master端,创建并关闭文件( fmCreate标志不能与其他标志一起使用!),然后在fmOpenWrite模式下使用fmShareDenyWrite共享重新打开它, fmOpenWrite打开状态,并在需要时写入。

On the Slave side, open the file in fmOpenRead mode with fmShareDenyNone sharing, leave it open, and read from it every second. 在Slave端,使用fmShareDenyNone共享以fmOpenRead模式打开文件, fmOpenRead打开状态,并每秒读取一次。 No need to copy the entire shared file over the network every time. 无需每次都通过网络复制整个共享文件。 That is wasted bandwidth. 这是浪费的带宽。 Just read whatever new data has been written in the past few seconds and that is all. 只需读取过去几秒内写入的所有新数据即可。 If the Slave needs the data to be stored in a local file, then it can manage a separate local file independantly of the shared file, pushing new data into the local file when needed. 如果Slave需要将数据存储在本地文件中,那么它可以独立于共享文件管理单独的本地文件,在需要时将新数据推送到本地文件中。

To deal with your specific occasional recurring problem: 处理您特定的偶然重复出现的问题:

You don't say what version of Delphi you are using. 你没有说你正在使用什么版本的Delphi。

There is a bug in the TFileStream.Create() constructor up to and including version 2007 (at least). TFileStream.Create()构造函数中有一个错误,包括2007版本(至少)。 This might explain your occasional concurrency problems. 这可以解释您偶尔出现的并发问题。

Having said that, I believe the bug is more likely to result in files not being created as expected (when a ShareMode is additionally specified), tho this may then in turn lead to your concurrency problem. 话虽如此,我认为该错误更有可能导致文件未按预期创建(当另外指定ShareMode时),这可能反过来导致您的并发问题。

One way around this might be when the file needs to be created, first create the file then simply open it for writing as a separate constructor call - this actually makes file creation a separate step, with file writing a consistent part of the process: 解决这个问题的一种方法可能是在需要创建文件时,首先创建文件然后只需打开它作为单独的构造函数调用 - 这实际上使文件创建成为一个单独的步骤,文件编写了流程的一致部分:

  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;

Use the standard Append file creation/opening command, use write to update the log, and close the file immediately. 使用标准附加文件创建/打开命令,使用write更新日志,并立即close文件。

Use a job on the operating system to copy/move the files; 使用操作系统上的作业来复制/移动文件; have it retry and to launch at a frequency greater than what you require. 让它重试并以超出您要求的频率发射。

If you want to do it from within Delphi then use MoveFile to move the whole thing. 如果你想从Delphi中做到这一点,那么使用MoveFile来移动整个东西。

You might want to wrap both the log writes and the moves in try-except so they can be retried a reasonable number of times if the file system (NTFS on Windows?) doesn't resolve the concurrency for you. 您可能希望将日志写入和移动都包装在try-except这样如果文件系统(Windows上的NTFS?)不能解析并发性,它们可以重试合理的次数。 In the worst case, either: 在最坏的情况下,要么:

  1. The file got moved and it gets recreated and written to. 文件被移动并重新创建并写入。
  2. The file is not moved right away because it is being written to. 由于正在写入文件,因此不会立即移动该文件。

If the OS doesn't resolve the race condition, then you will have to give priority to the starved action using a semaphore/lock. 如果操作系统无法解决竞争条件,则必须使用信号量/锁定优先考虑饥饿操作。

search for a function called "IsFileInUse" or something similar, I'm sure you can use that like: 搜索一个名为“IsFileInUse”的函数或类似的东西,我相信你可以使用它:

// master
while IsFileInUse(*AFileName*) do
  Sleep(10);
write-content-to-file

// slave
while IsFileInUse(*AFileName*) do
  Sleep(10);
copy-file-to-a-special-location

and presto!! 和presto !! you're done!! 你完成了!!

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM