无法通过 Java 中的套接字发送大文件

Posted

技术标签:

【中文标题】无法通过 Java 中的套接字发送大文件【英文标题】:Can't send large files over socket in Java 【发布时间】:2012-07-12 01:25:38 【问题描述】:

我有工作的服务器和客户端应用程序,它们在发送小文件时工作完美,但是当我尝试通过套接字发送例如 700mb 的电影文件时,它给了我

Exception in thread "AWT-EventQueue-0" java.lang.OutOfMemoryError: Java heap space

我在网上搜索了一些关于发送大文件的教程,但不太明白,但我认为我的问题是在写文件。

这是服务器用来编写我的文件的代码:

output = new FileOutputStream(directory + "/" + fileName);
            long size = clientData.readLong();
            byte[] buffer = new byte[1024];

            while (size > 0 && (bytesRead = clientData.read(buffer, 0, (int) Math.min(buffer.length, size))) != -1) 
                output.write(buffer, 0, bytesRead);
                size -= bytesRead;
            
            output.close();

这是我的客户用来发送文件的代码:

byte[] fileLength = new byte[(int) file.length()];  

        FileInputStream fis = new FileInputStream(file);  
        BufferedInputStream bis = new BufferedInputStream(fis);

        DataInputStream dis = new DataInputStream(bis);     
        dis.readFully(fileLength, 0, fileLength.length);  

        OutputStream os = socket.getOutputStream();  

        //Sending size of file.
        DataOutputStream dos = new DataOutputStream(os);   
        dos.writeLong(fileLength.length);
        dos.write(fileLength, 0, fileLength.length);     
        dos.flush();  

        socket.close();  

【问题讨论】:

你能做的最好的事情就是像这样运行你的程序***.com/questions/542979/… 然后你可以使用 jvisualvm 来分析它。 我认为您的问题是您试图将 X MB 的内容查找到 Y MB 内存中,其中 X > Y。如果这是真的,您如何编写该文件并不重要。这就是异常告诉你的。 你想一口气吃掉一个大西瓜,所以你死定了。尝试读取一小段文件并将其发送出去并重复操作。 Sending large files over socket的可能重复 【参考方案1】:

它给你OutOfMemoryError,因为你试图在发送之前将整个文件读入内存。这是 100% 完全没有必要的。只需读取和写入块,就像您在接收代码中所做的那样。

【讨论】:

你能编辑我的代码吗?因为 readFully() 是我知道如何做到这一点的唯一方法。谢谢 @RohitMalish 它不是“[你]知道怎么做的唯一方法”。看看你自己的接收代码。这就是如何做到这一点。你已经写好了。只需删除 readLong() 部分以及从它流出的所有内容:阅读直到 EOF。 好吧,我曾经尝试过这种方法,但它给了我不同类型的错误,例如当我收到文件时文件为空等 @RohitMalish 我只能建议你再试一次,现在你对自己在做什么有了一些了解。【参考方案2】:

问题是您试图一次将整个文件加载到内存中(通过readFully),这超出了您的堆空间(我认为默认为 256mb)。

你有两个选择:

    不错的选择:以块的形式加载文件(例如,一次 1024 个字节)并像这样发送。实施起来需要更多的努力。 错误选项:通过 -Xmx 选项增加堆空间。不推荐,我主要提到这一点,以防您需要更多的堆空间来运行程序,但在这种情况下,这是一个非常糟糕的主意。

对于选项一,您可以尝试以下方法:

DataInputStream in = new DataInputStream(new FileInputStream("file.txt"));
byte[] arr = new byte[1024];
try 
    int len = 0;
    while((len = in.read(arr)) != -1)
    
        // send the first len bytes of arr over socket.
     
 catch(IOException ex) 
    ex.printStackTrace();

【讨论】:

我不是很喜欢 java,但是对于 700MB 的文件来说,1024 是不是太小了?它会产生很多调用.. @Karoly Horvath:这只是一个例子。我使用了 1024,因为这是他在接收方使用的值。 现在它在发送大文件时不会抱怨,但是例如现在我收到空的小文件,并且较大的文件(例如视频)即使显示为 700+ MB,也无法播放,知道为什么吗? @Rohit Malish:嗯……这看起来很奇怪。也许你应该用你现在的代码打开另一个问题,因为你不可能在这个问题上吸引更多的访问者,因为它是关于一个不同的问题。【参考方案3】:

OutOfMemoryError 在您的程序达到堆大小限制时抛出。您将整个文件加载到 RAM 中。默认堆大小是物理内存大小的 1/4 或 1GB,因此您的程序达到了限制(您可能有 2GB RAM,因此堆大小为 512MB)。

您应该分块读取文件(例如 10MB),这样您就不会达到堆大小限制,并且您可以在出现错误时重新发送块,而不是重新发送整个文件。您甚至可以在不同的线程中读取块,因此当您发送 1 个块时,您可以加载第二个块,当您发送第一个块时,您可以立即开始发送第二个块。

【讨论】:

所以你的堆大小是 1GB。您可能会达到限制,因为您不仅将内存用于文件,而且还用于其他事物(例如 JVM、GUI 等)【参考方案4】:

问题是在您的客户端中,您为整个文件创建了一个字节数组。

byte[] fileLength = new byte[(int) file.length()];  //potentially huge buffer allocated here

您应该在服务器端做同样的事情,将文件逐块读取到固定大小的缓冲区中。

【讨论】:

【参考方案5】:

就像您在服务器端使用缓冲区发送数据一样,在您的小程序/客户端中使用缓冲区来读取它。如果你正在传输一个大文件,当你使用readFully() 时,你显然会耗尽内存。此方法仅适用于您知道您的数据将非常非常很小的情况。

【讨论】:

以上是关于无法通过 Java 中的套接字发送大文件的主要内容,如果未能解决你的问题,请参考以下文章

通过Socket.SendFile发送大文件

通过socket android发送大文件

如何递归遍历目录,通过 node.js 中的套接字发送所有文件名?

C# Sockets,接收大文件

Android中Socket大文件断点上传

在 Qt 中通过 TCP 传输大文件