2

好的,所以我正在制作一个具有服务器和客户端的 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);
        }
    }

那么,在写入文件之前,我如何确定完整的存档已经存在呢?

4

5 回答 5

2

On the sending side write the file size before you start writing the file. On the reading side Read the file size so you know how many bytes to expect. Then call read until you have gotten everything you expect. With network sockets it may take more than one call to read to get everything that was sent. This is especially true as your data gets larger.

于 2013-09-12T15:03:33.730 回答
0

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

于 2013-09-12T15:08:50.033 回答
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在实际文件之前发送文件的大小。我没有对此进行测试,但它应该可以工作。

于 2013-09-12T15:19:22.040 回答
0

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

通过修复您的代码。您正在InputStream.available()用作流结束的测试。那不是它的用途。将您的复制循环更改为此,这也简单得多:

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

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

于 2013-09-13T03:05:48.573 回答
-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

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

更新如果您想读取多个文件,则将您流式传输到“数据包”中,并为每个文件添加一个整数大小的前缀。然后,您读取直到收到 size 个字节而不是in.read = -1.

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

于 2013-09-12T16:01:06.027 回答