文件下载后循环不退出
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);
}
}
我有以下代码来下载通过 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);
}
}