【问题标题】:While loop doesn't exit after file download文件下载后循环不退出
【发布时间】:2021-08-31 03:11:23
【问题描述】:

我有以下代码来下载通过 TCP 传输的文件:

        try (OutputStream out = new FileOutputStream(path); InputStream is = socket.getInputStream();) {
            byte[] bytes = new byte[1024];
            int count, xp = 0;
            while ((count = is.read(bytes)) > 0) {      // TODO after upload the service doesn't leave while loop
                out.write(bytes, 0, count);
            }
            System.out.println("hello");
         ...

上传代码:

if (ready.equalsIgnoreCase(CdnResponse.READY.getContext())){
    int read = 0;
    byte[] bytes = new byte[1024];
    while ((read = inputStream.read(bytes)) != -1) {
        out.write(bytes, 0, read);
    }

}

上传正常退出循环。

一旦处理完所有字节(它们总是被成功处理,但循环永远不会退出),文件被创建,没有任何问题,但循环不会退出。

【问题讨论】:

  • 写完字节后是否正确关闭inputStream
  • 上传输入流是什么?

标签: java file tcp fileoutputstream


【解决方案1】:

TCP/IP 连接被设计为长期流式连接(建立在无序、无保证、基于数据包的 IP 协议之上)。

这意味着is.read(bytes) 完全按照规范所说的那样做:它将等待至少 1 个字节可用,“流结束”信号进来。只要两者都没有发生(没有字节到达,但流没有关闭),它将尽职尽责地阻塞。如果必须的话,永远。

解决方案是 [A] 预先发送文件的大小,然后调整循环以在收到该数量的字节后退出,或者 [B] 关闭流。

要关闭流,请关闭套接字。听起来你不想那样做(你在流上多路复用多个东西,即在传输文件后,你可以发送其他命令)。

所以,选项 A 听起来更好。但是,选项 A 的先决条件是您知道要从 inputStream 中输出多少字节。如果它是一个文件,那很容易,只需询问它的大小。如果是流式数据,则需要在“上传代码端”首先将整个数据流式传输到一个文件中,然后才通过网络将其流式传输,这既笨拙又可能效率低下。

如果你知道大小,它看起来像(我将在这里使用更新的 API,你使用的是一些过时的、有 20 年历史的过时的东西):

// upload side
void upload() {
  Path p = Paths.get("/path/to/file/you/want/to/send");
  long fileSize = Files.size(p);
  out.write(longToBytes(fileSize);
  try (InputStream in = Files.newInputStream(p)) {
    in.transferTo(out);
  }
}

public static byte[] longToBytes(long x) {
    ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
    buffer.putLong(x);
    return buffer.array();
}

此代码具有以下属性:

  • 首先它发送 8 个字节,以大端顺序,即即将到来的数据的大小。
  • 它使用新的java.nio.file API。
  • 它使用 InputStream 中的新 transferTo 方法,避免了必须声明一个字节数组作为缓冲区和一个 while 循环的繁琐。

然后在下载端:

void download() {
  long size = bytesToLong(in.readNBytes(8));
  Path p = Paths.get("/file/to/store/data/at");
  // Generally network transfer sizes are a bit higher than 1k;
  // up to 64k in fact. Best to declare a larger buffer!
  byte[] bb = new byte[65536];
  try (OutputStream out = Files.newOutputStream(p)) {
    while (size > 0) {
      int count = in.read(bb);
      if (count == -1) throw new IOException("Unexpected end of stream");
      out.write(bb, 0, count);
      size -= count;
    }
  }
}

public static long bytesToLong(byte[] bytes) {
    ByteBuffer buffer = ByteBuffer.allocate(Long.BYTES);
    buffer.put(bytes);
    buffer.flip();//need flip 
    return buffer.getLong();
}

这段代码:

  • 首先使用新的readNBytes 方法读取该尺寸。

如果您知道传入的数据有多大,则需要编写一个小协议。例如:

  • 以 2 个字节的形式发送大小,以大端顺序,无符号。然后是这么多字节,然后无限发送另一个大小。
  • 当流完成时,发送一个大小为 0 的字节(因此,2 个字节,值为 0),表示文件已完成。

如果您需要,我将把它作为练习留给您实现上传和下载方面。

【讨论】:

  • 我正在做的是从我的 j2ee web 服务器接受一个文件,然后在 j2ee 的 servlet 中将字节发送到我的远程服务器
  • 我没有将文件保存在网络服务器上,只是目的地,
  • 我从来没有说过你应该 - 我说:为了让这个模型(和这段代码)工作,服务器需要知道它会在开始发送之前发送多少字节。这不是 InputStreams 的特性,但它是文件的特性。因此,如果您的网络服务器有文件,那就太好了。
【解决方案2】:

我按照@rzwitserloot [A] 解决方案解决了这个问题。

以下是我更新的代码: 在通过 TCP 套接字向我的 CDN 发送字节之前,我以字节为单位发送文件大小

        try (OutputStream out = new FileOutputStream(path)) {
            final InputStream is = socket.getInputStream();
            byte[] bytes = new byte[1024];
            int count;
            long receivedBytes = 0;
            while (receivedBytes < byteSize && (count = is.read(bytes)) > 0) {
               // if ((count = is.read(bytes)) <= 0) break;
                out.write(bytes, 0, count);
                receivedBytes = receivedBytes + count;
                //System.out.println(count + "   " + receivedBytes + "/" + byteSize);
            }
        }

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2023-02-22
    • 2013-03-01
    • 2018-06-27
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2015-09-10
    • 1970-01-01
    相关资源
    最近更新 更多