通过Windows远程桌面(tsclient)编写Java时创建HUGE文件

Uhl*_*len 17 java windows file-io remote-desktop

当我们的Swing应用程序通过Windows远程桌面将文件写入用户本地计算机时,我们的客户报告了一个非常奇怪的问题(该应用程序托管在用户连接的终端服务器上).

流程是:

  • 用户通过远程桌面登录并运行应用程序(C:\包含在"本地资源"中)
  • 在工作时,他们将数据从数据库导出到文件中
  • 用户选择要导出的数据
  • 用户在本地计算机上选择目标文件 \\tsclient\C\Temp\TestFile.txt
  • 文件可能很大,因此从数据库中提取1000行并按批次写入文件
  • 在第二批,当Java打开文件并再次写入文件时,一些非常奇怪的东西开始发生!
    • 文件大小迅速增加,停止在2 GB左右
    • 然后继续将数据写入文件

我不确定这是核心Java库,远程桌面实现还是组合中的问题.我们的应用程序也通过Citrix托管,工作正常,写入本地磁盘或UNC网络路径也可以正常工作.

我已经创建了一个SSCCE来演示这个问题,用远程桌面连接到计算机(确保C:\是"本地资源")并运行该程序以查看一些非常奇怪的行为!我正在使用JDK-7u45.

import static java.nio.file.StandardOpenOption.APPEND;
import static java.nio.file.StandardOpenOption.CREATE;
import static java.nio.file.StandardOpenOption.TRUNCATE_EXISTING;
import static java.nio.file.StandardOpenOption.WRITE;

import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import java.nio.file.Files;
import java.nio.file.OpenOption;
import java.util.Collections;

/**
 * Demonstrates weird issue when writing (appending) to a file over TsClient (Microsoft Remote Desktop).
 * 
 * @author Martin
 */
public class WriteOverTsClientDemo
{
    private static final File FILE_TO_WRITE = new File("\\\\tsclient\\C\\Temp\\TestFile.txt");
    //private static final File FILE_TO_WRITE = new File("C:\\Temp\\TestFile.txt");

    private static final String ROW_DATA = "111111111122222222223333333333444444444555555555566666666667777777777888888888899999999990000000000";

    public static void main(String[] args) throws IOException
    {
        if (!FILE_TO_WRITE.getParentFile().exists())
        {
            throw new RuntimeException("\nPlease create directory C:\\Temp\\ on your local machine and run this application via RemoteDesktop with C:\\ as a 'Local resource'.");
        }
        FILE_TO_WRITE.delete();
        new WriteOverTsClientDemo().execute();
    }

    private void execute() throws IOException
    {
        System.out.println("Writing to file: " + FILE_TO_WRITE);
        System.out.println();

        for (int i = 1; i <= 10; i++)
        {
            System.out.println("Writing batch " + i + "...");
            writeDataToFile(i);
            System.out.println("Size of file after batch " + i + ": " + FILE_TO_WRITE.length());
            System.out.println();
        }
        System.out.println("Done!");
    }

    private void writeDataToFile(int batch) throws IOException
    {
        Charset charset = Charset.forName("UTF-8");
        CharsetEncoder encoder = charset.newEncoder();

        try(OutputStream out = Files.newOutputStream(FILE_TO_WRITE.toPath(), CREATE, WRITE, getTruncateOrAppendOption(batch));
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(out, encoder)))
        {
            writeData(batch, writer);
        }
    }

    private void writeData(int batch, BufferedWriter writer) throws IOException
    {
        for (String data : createData())
        {
            writer.append(Integer.toString(batch));
            writer.append(" ");
            writer.append(data);
            writer.append("\n");
        }
    }

    private Iterable<String> createData()
    {
        return Collections.nCopies(100, ROW_DATA);
    }

    /**
     * @return option to write from the beginning or from the end of the file
     */
    private OpenOption getTruncateOrAppendOption(int batch)
    {
        return batch == 1 ? TRUNCATE_EXISTING : APPEND;
    }
}
Run Code Online (Sandbox Code Playgroud)

Jan*_*Jan 8

我没有设置(没有Windows)来验证这种效果:(所以只是想法:

2GB听起来像文件系统相关的最大文件大小.客户端的32位Windows操作系统?

这种行为听起来就像在坏块FS上的聪明的文件系统缓存:远程的快速文件写入访问尝试巧妙地占用文件以试图将未来的写入固定到具有块的文件.尝试使用其他FS进行验证?试过FreeRDP?

保持文件打开.重写大块的写入可能暗示聪明的系统要缓存.

更新:

FileChannelImpl.java:248

// in append-mode then position is advanced to end before writing
p = (append) ? nd.size(fd) : position0(fd, -1);
Run Code Online (Sandbox Code Playgroud)

最后导致 FileDispatcherImpl:136

static native long More ...size0(FileDescriptor fd) throws IOException;
Run Code Online (Sandbox Code Playgroud)

什么本地人可以容纳任何错误.谈到中间的协议.我会将此文件归为nio/Windows中的错误,因为他们可能没有预见到RDP下面的任何有趣的事情.

它看起来像返回的大小Integer.MAX_VALUE,文件指针移动到那里......

替代实现java.io.FileWriter和无编码以减少代码行:

import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.Collections;

/**
 * Demonstrates weird issue when writing (appending) to a file over TsClient (Microsoft Remote Desktop).
 *
 * @author Martin
 */
public class WriteOverTsClientDemo
{
   // private static final File FILE_TO_WRITE = new File("\\\\tsclient\\C\\Temp\\TestFile.txt");
   private static final File FILE_TO_WRITE = new File("/tmp/TestFile.txt");

   private static final String ROW_DATA = "111111111122222222223333333333444444444555555555566666666667777777777888888888899999999990000000000";

   public static void main(final String[] args) throws IOException
   {
      if (!FILE_TO_WRITE.getParentFile().exists())
      {
         throw new RuntimeException("\nPlease create directory C:\\Temp\\ on your local machine and run this application via RemoteDesktop with C:\\ as a 'Local resource'.");
      }
      FILE_TO_WRITE.delete();
      new WriteOverTsClientDemo().execute();
   }

   private void execute() throws IOException
   {
      System.out.println("Writing to file: " + FILE_TO_WRITE);
      System.out.println();

      for (int i = 1; i <= 20; i++)
      {
         System.out.println("Writing batch " + i + "...");
         writeDataToFile(i);
         System.out.println("Size of file after batch " + i + ": " + FILE_TO_WRITE.length());
         System.out.println();
      }
      System.out.println("Done!");
   }

   private void writeDataToFile(final int batch) throws IOException
   {
      try (BufferedWriter writer = new BufferedWriter(new FileWriter(FILE_TO_WRITE, batch > 1)))
      {
         writeData(batch, writer);
      }
   }

   private void writeData(final int batch, final BufferedWriter writer) throws IOException
   {
      for (final String data : createData())
      {
         writer.append(Integer.toString(batch));
         writer.append(" ");
         writer.append(data);
         writer.append("\n");
      }
   }

   private Iterable<String> createData()
   {
      return Collections.nCopies(100, ROW_DATA);
   }

}
Run Code Online (Sandbox Code Playgroud)