Java在通过Windows远程桌面(tsclient)写入时创建巨大的文件

2022-09-02 22:31:01

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

流程为:

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

我不确定这是核心Java库,远程桌面实现还是组合中的问题。我们的应用程序也通过 Citrix 托管,该功能正常,写入本地磁盘或 UNC 网络路径也运行良好。

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

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;
    }
}

答案 1

我没有设置(无窗口)来验证此效果:(所以只是想法:

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

这种行为听起来像是坏块FS上的智能文件系统缓存:对大块的快速文件写入访问远程尝试巧妙地占据文件,以试图将将来对具有块的文件的写入加快速度。尝试使用其他 FS 进行验证?尝试过 FreeRDP?

使文件保持打开状态。重新打开以写入巨大的块可能会暗示聪明的系统要缓存。

更新:

文件通道Impl.java:248

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

最终导致文件巡视器Impl: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);
   }

}

答案 2

我们有这个完全相同的问题,一位客户报告说,我们的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)

若要写入文件末尾,请将重叠结构的“偏移量”和“偏移量高”成员指定为0xFFFFFFFF。这在功能上等效于之前调用 CreateFile 函数以使用FILE_APPEND_DATA访问打开 hFile。

以下 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;
}