如何确保通过套接字流收到整个文件?

时间:2013-09-12 14:54:40

标签: java sockets networking

好的,所以我正在制作一个有服务器和客户端的Java程序,我正在从服务器向客户端发送一个Zip文件。我几乎把文件发送下来了。但接受我发现有些不一致。我的代码并不总是获得完整的存档。我猜它是在BufferedReader完成之前终止的。这是客户端的代码:

public void run(String[] args) {
        try {
            clientSocket = new Socket("jacob-custom-pc", 4444);
            out = new PrintWriter(clientSocket.getOutputStream(), true);
            in = new BufferedInputStream(clientSocket.getInputStream());
            BufferedReader inRead = new BufferedReader(new InputStreamReader(in));
            int size = 0;
            while(true) {

                if(in.available() > 0) {    

                        byte[] array = new byte[in.available()];
                        in.read(array);
                        System.out.println(array.length);


                        System.out.println("recieved file!");
                        FileOutputStream fileOut = new FileOutputStream("out.zip");
                        fileOut.write(array);
                        fileOut.close();
                        break;
                    }
                }
            }
        } catch(IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }

那么在编写文件之前如何确定完整存档?

6 个答案:

答案 0 :(得分:2)

在开始编写文件之前,在发送方写入文件大小。在读取端读取文件大小,以便知道预期的字节数。然后打电话给读,直到你得到你所期望的一切。使用网络套接字,可能需要多次调用才能获取发送的所有内容。当您的数据变大时尤其如此。

答案 1 :(得分:0)

HTTP以字节为单位发送content-length: x + \ n。这很优雅,如果conn被破坏,它可能会抛出TimeoutException。

答案 2 :(得分:0)

您正在使用TCP套接字。 ZIP文件可能比网络MTU大,因此它将被分成多个数据包并在另一侧重新组装。不过,这样的事情可能会发生:

  • 客户端连接
  • 服务器开始发送。 ZIP文件比MTU大,因此分成多个数据包。
  • 客户端忙于等待while (true),直到收到第一个数据包。
  • 客户注意到数据已到达(in.available() > 0
  • 客户端读取所有可用数据,将其写入文件并退出
  • 最后一个数据包到达

正如您所看到的:除非客户端计算机速度很慢并且网络速度非常快并且具有巨大的MTU,否则您的代码将无法接收整个文件按设计。这就是你构建它的方式。

另一种方法:使用长度预先填充数据。

Socket clientSocket = new Socket("jacob-custom-pc", 4444);
DataInputStream dataReader = new DataInputStream(clientSocket.getInputStream());
FileOutputStream out = new FileOutputStream("out.zip");
long size = dataReader.readLong();
long chunks = size / 1024;
int lastChunk = (int)(size - (chunks * 1024));
byte[] buf = new byte[1024];
for (long i = 0; i < chunks; i++) {
    dataReader.read(buf);
    out.write(buf);
}
dataReader.read(buf, 0, lastChunk);
out.write(buf, 0, lastChunk);

服务器使用DataOutputStream在实际文件之前发送文件大小。我没有对此进行测试,但它应该有效。

答案 3 :(得分:0)

  

如何确保通过套接字流收到整个文件?

修复你的代码。您正在使用InputStream.available()作为流结束的测试。这不是它的用途。将您的副本循环更改为此,这也简化了很多:

while ((count = in.read(buffer)) > 0)
{
    out.write(buffer, 0, count);
}

使用任何大于零的缓冲区大小,通常为8192。

答案 4 :(得分:-1)

发送时在文件末尾添加终止字符。我相信Unix使用-1来表示文件的结尾。收到文件后,请等到找到-1然后将其删除。

答案 5 :(得分:-1)

In.available()只是告诉您in.read()目前没有阻止(等待)的数据,但这并不意味着流的结束。但是,他们可能随时使用TCP / IP数据包进入您的PC。通常,您从不使用in.available()In.read()足以完全读取流的所有内容。读取输入流的模式是

byte[] buf;
int size;

while ((size = in.read(buf)) != -1)
  process(buf, size);

// end of stream has reached

这样,您将完全读取流,直到结束。

更新如果您想要读取多个文件,那么您将流式传输到&#34;数据包&#34;并以每个整数大小为前缀。然后读取直到收到大小字节而不是in.read = -1

update2 无论如何,永远不要使用in.available来区分数据块。如果这样做,则表示传入数据之间存在时间延迟。您只能在实时系统中执行此操作。但Windows,Java和TCP / IP都是这些层与实时不兼容的。