Java——如何从输入流(socket/socketServer)读取未知数量的字节?
希望使用 inputStream 通过套接字读取一些字节。服务器发送的字节数量可能是可变的,并且客户端事先并不知道字节数组的长度。如何实现这一点?
byte b[];
sock.getInputStream().read(b);
这会导致 Net BzEAnSZ 出现“可能未初始化错误”。帮助。
Looking to read in some bytes over a socket using an inputStream. The bytes sent by the server may be of variable quantity, and the client doesn't know in advance the length of the byte array. How may this be accomplished?
byte b[];
sock.getInputStream().read(b);
This causes a 'might not be initialized error' from the Net BzEAnSZ. Help.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

发布评论
评论(11)
无需重新发明轮子,使用 Apache Commons:
IOUtils.toByteArray(inputStream);
例如,带有错误处理的完整代码:
public static byte[] readInputStreamToByteArray(InputStream inputStream) {
if (inputStream == null) {
// normally, the caller should check for null after getting the InputStream object from a resource
throw new FileProcessingException("Cannot read from InputStream that is NULL. The resource requested by the caller may not exist or was not looked up correctly.");
}
try {
return IOUtils.toByteArray(inputStream);
} catch (IOException e) {
throw new FileProcessingException("Error reading input stream.", e);
} finally {
closeStream(inputStream);
}
}
private static void closeStream(Closeable closeable) {
try {
if (closeable != null) {
closeable.close();
}
} catch (Exception e) {
throw new FileProcessingException("IO Error closing a stream.", e);
}
}
其中 FileProcessingException
是您的应用程序特定的有意义的 RT 异常,它将不间断地传输到正确的处理程序,不会污染之间的代码。
简单的答案是:
byte b[] = new byte[BIG_ENOUGH];
int nosRead = sock.getInputStream().read(b);
其中 BIG_ENOUGH
足够大。
但总的来说,这存在一个很大的问题。单个read
调用不保证返回另一端已写入的所有内容。
如果
nosRead
值为BIG_ENOUGH
,您的应用程序无法确定是否还有更多字节即将到来;另一端可能恰好发送了BIG_ENOUGH
字节...或超过BIG_ENOUGH
字节。在前一种情况下,如果您尝试读取,您的应用程序将(永远)阻塞。在后一种情况下,您的应用程序必须(至少)执行另一次读取
才能获取其余数据。如果
nosRead
值小于BIG_ENOUGH
,您的应用程序仍然不知道。它可能已收到所有数据,部分数据可能已延迟(由于网络数据包碎片、网络数据包丢失、网络分区等),或者另一端可能在发送数据的过程中阻塞或崩溃。< /p>
最好的答案是要么您的应用程序需要事先知道需要多少字节,或者应用程序协议需要以某种方式告诉应用程序如何预期的字节数或所有字节均已发送时。
可能的方法是:
- 应用程序协议使用固定消息大小(不适用于您的示例)
- 应用程序协议消息大小在消息标头中指定
- 应用程序协议使用消息结束标记
- 应用程序协议不是基于消息的,并且另一端关闭连接以表示结束。
如果没有这些策略之一,您的应用程序就只能猜测,并且偶尔可能会出错。
然后您使用多个读取调用和(可能)多个缓冲区。
将所有输入数据流式传输到输出流。这是工作示例:
InputStream inputStream = null;
byte[] tempStorage = new byte[1024];//try to read 1Kb at time
int bLength;
try{
ByteArrayOutputStream outputByteArrayStream = new ByteArrayOutputStream();
if (fileName.startsWith("http"))
inputStream = new URL(fileName).openStream();
else
inputStream = new FileInputStream(fileName);
while ((bLength = inputStream.read(tempStorage)) != -1) {
outputByteArrayStream.write(tempStorage, 0, bLength);
}
outputByteArrayStream.flush();
//Here is the byte array at the end
byte[] finalByteArray = outputByteArrayStream.toByteArray();
outputByteArrayStream.close();
inputStream.close();
}catch(Exception e){
e.printStackTrace();
if (inputStream != null) inputStream.close();
}
这是使用 ByteArrayOutputStream 的一个更简单的示例...
socketInputStream = socket.getInputStream();
int expectedDataLength = 128; //todo - set accordingly/experiment. Does not have to be precise value.
ByteArrayOutputStream baos = new ByteArrayOutputStream(expectedDataLength);
byte[] chunk = new byte[expectedDataLength];
int numBytesJustRead;
while((numBytesJustRead = socketInputStream.read(chunk)) != -1) {
baos.write(chunk, 0, numBytesJustRead);
}
return baos.toString("UTF-8");
但是,如果服务器不返回 -1,您将需要以其他方式检测数据的结尾 - 例如,返回的内容可能总是以某个标记结尾(例如,“”),或者您可以使用socket.setSoTimeout()来解决。 (提到这一点似乎是一个常见问题。)
这既是一个迟来的答案,也是自我广告,但任何检查这个问题的人可能想看看这里:
https://github.com/GregoryConrad/SmartSocket
这个问题已经有7年历史了,但我在制作 NIO< 时遇到了类似的问题/a> 和 OIO 兼容系统(客户端和服务器可能是他们想要的任何东西,OIO 或 NIO)。
由于输入流阻塞,这就退出了挑战。
我找到了一种方法,这使得它成为可能,并且我想将其发布,以帮助有类似问题的人。
读取动态 sice 的字节数组是通过 完成的DataInputStream,它可以简单地包裹在socketInputStream 周围。另外,我不想引入特定的通信协议(比如首先发送将要发送的字节大小),因为我想让它尽可能简单。首先,我有一个简单的实用程序 Buffer 类,它看起来像这样:
import java.util.ArrayList;
import java.util.List;
public class Buffer {
private byte[] core;
private int capacity;
public Buffer(int size){
this.capacity = size;
clear();
}
public List<Byte> list() {
final List<Byte> result = new ArrayList<>();
for(byte b : core) {
result.add(b);
}
return result;
}
public void reallocate(int capacity) {
this.capacity = capacity;
}
public void teardown() {
this.core = null;
}
public void clear() {
core = new byte[capacity];
}
public byte[] array() {
return core;
}
}
这个类仅存在,因为愚蠢的方式,字节 <=> Java 中的字节自动装箱可使用此列表。在这个例子中根本不需要这样做,但我不想在这个解释中遗漏一些东西。
接下来是 2 个简单的核心方法。在这些中,StringBuilder 用作“回调”。它将填充已读取的结果,并返回读取的字节数。当然,这可能会以不同的方式进行。
private int readNext(StringBuilder stringBuilder, Buffer buffer) throws IOException {
// Attempt to read up to the buffers size
int read = in.read(buffer.array());
// If EOF is reached (-1 read)
// we disconnect, because the
// other end disconnected.
if(read == -1) {
disconnect();
return -1;
}
// Add the read byte[] as
// a String to the stringBuilder.
stringBuilder.append(new String(buffer.array()).trim());
buffer.clear();
return read;
}
private Optional<String> readBlocking() throws IOException {
final Buffer buffer = new Buffer(256);
final StringBuilder stringBuilder = new StringBuilder();
// This call blocks. Therefor
// if we continue past this point
// we WILL have some sort of
// result. This might be -1, which
// means, EOF (disconnect.)
if(readNext(stringBuilder, buffer) == -1) {
return Optional.empty();
}
while(in.available() > 0) {
buffer.reallocate(in.available());
if(readNext(stringBuilder, buffer) == -1) {
return Optional.empty();
}
}
buffer.teardown();
return Optional.of(stringBuilder.toString());
}
第一个方法 readNext
将使用 DataInputStream 中的 byte[]
填充缓冲区,并返回以此方式读取的字节数。
在第二个方法中,readBlocking
,我利用了阻塞性质,不用担心消费者-生产者-问题。只需readBlocking
就会阻塞,直到收到新的字节数组。在调用这个阻塞方法之前,我们分配一个缓冲区大小。注意,我在第一次读取后调用了重新分配(在 while 循环内)。这是不需要的。您可以安全地删除此行,代码仍然可以工作。由于我的问题的独特性,我做到了。
我没有更详细解释的两件事是:
1. 在(DataInputStream 和这里唯一的短变量,抱歉)
2.断开连接(您的断开连接例程)
总而言之,您现在可以这样使用它:
// The in has to be an attribute, or an parameter to the readBlocking method
DataInputStream in = new DataInputStream(socket.getInputStream());
final Optional<String> rawDataOptional = readBlocking();
rawDataOptional.ifPresent(string -> threadPool.execute(() -> handle(string)));
这将为您提供一种通过套接字(或任何输入流)读取任何形状或形式的字节数组的方法。希望这有帮助!
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
您需要根据需要扩展缓冲区,通过读取字节块,一次读取 1024 个字节,如我不久前编写的示例代码所示
You need to expand the buffer as needed, by reading in chunks of bytes, 1024 at a time as in this example code I wrote some time ago