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

时间:2013-12-03 12:49:01

标签: 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;
    }
}

2 个答案:

答案 0 :(得分:8)

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

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

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

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

更新

FileChannelImpl.java:248

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

最终导致 FileDispatcherImpl:136

static native long More ...size0(FileDescriptor fd) throws IOException;

本机可以容纳任何错误。谈到中间的协议。我会将此文件归为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);
   }

}

答案 1 :(得分:0)

我们也遇到了同样的问题,一位客户报告说,我们的Java应用程序在写入TS客户端共享驱动器时会创建2GB文件。 我们注意到,仅当使用java.io.FileOutputStream和java.nio.Files.write追加数据时,才会发生此问题。

我们打开了一个问题,您可以在这里找到它:

https://bugs.java.com/bugdatabase/view_bug.do?bug_id=JDK-8206888

但是,在进一步调查之后,我们将问题归结为Windows WriteFile API的不当行为,这种行为在这种环境下会不满意文档中的内容:

(摘自https://docs.microsoft.com/en-gb/windows/desktop/api/fileapi/nf-fileapi-writefile

要写入文件的末尾,请将OVERLAPPED结构的Offset和OffsetHigh成员都指定为0xFFFFFFFF。这在功能上等同于之前调用FILE_APPEND_DATA访问hFile来打开CreateFile函数。

以下C程序可用于重现此问题:


#include <windows.h>
#include <stdio.h>

int main(int argc, char *argv[])
{
    if (argc < 2) {
        printf("Not enough args\n");
        return 1;
    }

    HANDLE hFile = CreateFile(argv[1], GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
    DWORD nw;
    OVERLAPPED ov;
    ov.Offset = (DWORD)0xFFFFFFFF;
    ov.OffsetHigh = (DWORD)0xFFFFFFFF;
    ov.hEvent = NULL;
    WriteFile(hFile, "a", 1, &nw, &ov);
    CloseHandle(hFile);

    return 1;
}