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),表示文件已完成。
如果您需要,我将把它作为练习留给您实现上传和下载方面。