5

我需要通过网络复制一个大文件(大约 20 MB)。通常,这不是问题,但是,与文件位于同一框的应用程序相当频繁地(大约每秒一次)写入文件。

因此,因为该文件经常被写入,所以File.Copy调用经常失败。我也试过File.ReadAllLines这似乎每次都有效,但需要永远。

有没有更好、更有效、更安全的方式通过网络复制文件?

PS 该文件正在由使用 Log4Net 的进程写入。而且,如果有人想知道,写作的过程不在我的掌控之中。

4

7 回答 7

1

您可以尝试这样处理:

string pathToFile = "path-to-your-file" ;
using ( Stream     s = File.Open( pathToFile , FileMode.Open , FileAccess.Read , FileShare.ReadWrite ) )
using ( TextReader r = new StreamReader( s ) )
{
    string fileContents = r.ReadToEnd() ;
    process( fileContents ) ;
}

但是,如果写入文件的进程不愿意共享它以供阅读,那么您就是 SOL。

然而 。. . 你说:

PS 该文件正在由使用 Log4Net 的进程写入。而且,如果有人想知道,写作的过程不在我的掌控之中。

您尝试访问的文件是由Log4Net Appender 生成的,例如FileAppenderRollingFileAppender吗?

如果是这样,默认的日志记录模型是FileAppender.ExclusiveLock,它使用排他锁在持续时间内保持文件打开,但是 IIRC,它仍然愿意共享它以供阅读。

但是获得所需结果(通过网络发送文件)的更简单(为什么要重新发明轮子?)方法是使用Log4Net 配置。您可以向源应用程序 Log4Net 配置文件添加一个附加程序,这将对您有所帮助。您可以使用:

  • RemotingAppender “旨在将事件传递到远程接收器。即实现 RemotingAppender.IRemoteLoggingSink 接口的任何对象。它使用 .NET 远程处理传递事件。通过设置 appenders Sink 属性来指定传递事件的对象。”

  • TelnetAppender “接受套接字连接并将日志消息流传输回客户端。输出以对 telnet 友好的方式提供,以便可以通过 TCP/IP 套接字监视日志。这允许对应用程序日志进行简单的远程监视。默认值端口是 23(telnet 端口)。”

无论哪种方式,您只需要编写一个简单的服务来处理入站日志消息。

就效率而言,如果是 log4net 输出,您只需要跟踪高水位标记并仅复制新内容即可。

  1. 打开源文件进行读取
  2. 创建目标文件
  3. 通过网络复制数据直到文件结束
  4. 保存流中的当前位置
  5. 睡觉
  6. 打开源文件进行读取
  7. 打开目标文件进行追加
  8. 寻找流中记住的位置(可能不再在文件末尾)
  9. 通过网络复制数据直到文件结束,附加到目标文件。
  10. 重复,令人作呕,从 #4

只是一个想法。

于 2012-08-20T22:49:36.383 回答
1

您可以在打开文件时使用文件共享字段以允许其他进程在您访问它时访问它,不幸的是,如果您的进程不是锁定文件的进程,那么您无法释放它并且您已经提到您不能更改编写代码,通过在文件共享字段中允许 r/w,您可以允许其他进程在复制文件时访问该文件。您可以编写一个小函数来检查文件是否被锁定并更改您的代码,以便在锁定时继续检查直到它解锁然后复制它。

于 2012-08-20T21:31:34.040 回答
1

File.Copy 可能无法获得文件的排他锁。

考虑到这File.Move是一个快速操作,即使通过网络也是如此。如果之前的应用程序不需要保留该文件,您可以运行

for (int i = 0; i < 10; i++)
{
    try {
        File.Move(logFilePath, tempPathOnRemoteServer);
    } catch (Exception) { 
        if (i == 9)
            throw new Exception("Could not acquire lock");
        System.Threading.Thread.Sleep(1000 * (i+1)); }
}
File.Copy(tempPathOnRemoteServer, endPathOnLocalHost);

假设日志记录应用程序可以在打开时从正在移动的文件中恢复,这应该不会有问题。

或者,如果无法移动文件,请使用与上述相同的代码,将 File.Move 替换为 File.Copy,但如果复制时间过长,您的独占锁可能会导致其他应用程序出现问题。

于 2012-08-20T21:35:04.687 回答
1

使用 FileStream 类读取文件,不要忘记在重载的构造函数中为 FileAccess 和 FileShare 设置适当的值。

FileStream(String, FileMode, FileAccess, FileShare)
于 2012-08-20T21:35:42.283 回答
0

如果您无法修改写入文件的程序,那么您的选择就会受到限制。想到的两种可能性是:

独占打开文件,循环复制:

using (var fin = File.Open(InputFilename, FileMode.Open, FileAccess.Read, FileShare.None))
{
    using (var fout = File.OpenWrite(OutputFilename))`
    {
        int bytesRead = 0;
        var buffer = new byte[65536];
        while ((bytesRead = fin.Read(buffer, 0, buffer.Length)) > 0)
        {
            fout.Write(buffer, 0, bytesRead);
        }
    }
}

如果 Log4Net 在您尝试打开文件进行读取时打开文件以进行写入,则您必须编写代码来处理引发的异常。该代码应该稍等片刻,然后重试。

这里的潜在缺点是,如果 Log4Net 无法写入文件(因为它是为独占访问而打开的),它可能会抛出异常。如果 Log4Net 不喜欢您打开文件以进行独占访问,那么...

只需重命名文件,然后在闲暇时复制它。如果在您尝试重命名时 Log4Net 已打开文件,您的代码将引发异常。你可以重试。如果 Log4Net 在您重命名文件后尝试写入文件,Log4Net 将创建一个具有该名称的新文件。

我没有看到任何其他选择。

顺便说一句,20 MB 并不是一个真正的大文件。在现代网络速度下,您应该能够在一秒钟内复制它。

于 2012-08-20T22:46:32.873 回答
0

好的,那么,另一种选择是始终模拟地写入两个文件。一个 - 真实的,另一个具有临时名称(比如说 GUID)。一旦您需要复制,请停止写入临时文件(而是创建一个新文件,然后继续在那里写入)。然后该文件将不再被锁定,因此您可以随心所欲地上传它。之后 - 删除它。

于 2012-08-21T07:58:55.010 回答
-1

首先在本地制作副本怎么样 - 这将非常快并且会保留它的状态,然后上传副本,最后删除副本?

于 2012-08-20T22:06:41.993 回答