文件下载后循环不退出

While loop doesn't exit after file download

我有以下代码来下载通过 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);
    }

}

上传正常退出循环。

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

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

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

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

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

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

如果您确实知道大小,它看起来像(我将在这里使用较新的 APIs,您使用的是一些过时的、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(因此,值为 0 的 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);
            }
        }