Java – 如何从inputStream(socket / socketServer)读取未知数量的字节?

希望使用inputStream在套接字上读取一些字节。 服务器发送的字节可以是可变数量的,并且客户端事先不知道字节数组的长度。 怎么可能完成?

byte b[]; sock.getInputStream().read(b); 

这会导致Net BzEAnSZ出现“可能未初始化错误”。 帮帮我。

读取一个int,它是接收的下一个数据段的大小。 创建具有该大小的缓冲区,或使用宽敞的预先存在的缓冲区。 读入缓冲区,确保它仅限于上述大小。 冲洗并重复:)

如果你真的不像你说的那样事先知道大小,请阅读扩展的ByteArrayOutputStream,正如其他答案所提到的那样。 但是,尺寸方法确实是最可靠的。

您需要根据需要扩展缓冲区,方法是读取大块的字节,一次1024个,就像我之前写的代码一样。

  byte[] resultBuff = new byte[0]; byte[] buff = new byte[1024]; int k = -1; while((k = sock.getInputStream().read(buff, 0, buff.length)) > -1) { byte[] tbuff = new byte[resultBuff.length + k]; // temp buffer size = bytes already read + bytes last read System.arraycopy(resultBuff, 0, tbuff, 0, resultBuff.length); // copy previous bytes System.arraycopy(buff, 0, tbuff, resultBuff.length, k); // copy current lot resultBuff = tbuff; // call the temp buffer as your result buff } System.out.println(resultBuff.length + " bytes read."); return resultBuff; 

假设发件人在数据末尾关闭流:

 ByteArrayOutputStream baos = new ByteArrayOutputStream(); byte[] buf = new byte[4096]; while(true) { int n = is.read(buf); if( n < 0 ) break; baos.write(buf,0,n); } byte data[] = baos.toByteArray(); 

简单的答案是:

 byte b[] = byte[BIG_ENOUGH]; int nosRead = sock.getInputStream().read(b); 

BIG_ENOUGH够大的地方。


但总的来说,这有一个很大的问题。 read方法无法保证返回另一端在单个调用中写入的所有内容。

  • 如果nosRead值为BIG_ENOUGH ,则您的应用程序无法确定是否还有更多字节; 另一端可能已经发送了BIG_ENOUGH字节…或者超过BIG_ENOUGH个字节。 在前一种情况下,如果您尝试阅读,您的应用程序将阻止(永远)。 在后一种情况下,您的应用程序必须(至少)执行另一次read以获取其余数据。

  • 如果nosRead值小于BIG_ENOUGH ,则您的应用程序仍然不知道。 它可能已收到所有内容,部分数据可能已被延迟(由于网络数据包碎片,网络数据包丢失,网络分区等),或者另一端可能已通过发送数据中途阻塞或崩溃。

最好的答案是您的应用程序要么需要事先知道要预期的字节数,要么“应用程序协议”需要以某种方式告诉它需要多少字节…或者所有字节都已发送。 可能的方法是:

  • 应用程序协议使用固定的邮件大小
  • 应用程序协议消息大小在消息头中指定
  • 应用程序协议使用消息结束标记
  • 应用程序协议不是基于消息的,另一端关闭连接以说“这就是结束”。

如果没有这些策略之一,您的应用程序就会被猜测,并且偶尔会出错。

无需重新发明轮子,使用Apache Commons:

 IOUtils.toByteArray(inputStream); 

例如,包含error handling的完整代码:

  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是您的特定于应用程序的有意义的RTexception,它将不间断地传递给您正确的处理程序,而不会污染其间的代码。

将所有输入数据流式传输到输出流。 这是工作示例:

  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(); } 

或者:

  1. 在传输字节后让发送方关闭套接字。 然后在接收器处继续阅读直到EOS。

  2. 根据Chris的建议,让发件人为长度字加前缀,然后读取那么多字节。

  3. 使用自描述协议,如XML,序列化,…

使用BufferedInputStream ,并使用available()方法返回可用于读取的字节大小,然后构造一个具有该大小的byte[] 。 问题解决了。 🙂

 BufferedInputStream buf = new BufferedInputStream(is); int size = buf.available(); 

这是一个使用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和OIO兼容系统(客户端和服务器可能是他们想要的任何东西,OIO或NIO)。

这是因为阻塞InputStreams而退出挑战。

我找到了一种方法,这使得它成为可能,我想发布它,以帮助有类似问题的人。

在这里使用DataInputStream读取动态sice的字节数组,它可以简单地包含在socketInputStream中。 此外,我不想引入特定的通信协议1(如首先发送将要发送的字节大小),因为我想尽可能使其成为香草。 首先,我有一个简单的实用程序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 list() { final List 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中的字节<=> Byte自动装箱与此List一起使用。 在这个例子中根本不需要这个,但我不想在这个解释中留下一些东西。

接下来,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 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[]并返回以这种方式读取的字节数。

在secon方法中, readBlocking ,我利用了阻塞性质,而不用担心消费者 – 生产者 – 问题 。 只需readBlocking将阻塞,直到接收到新的字节数组。 在我们调用这个阻塞方法之前,我们分配一个Buffer-size。 注意,我在第一次读取后调用reallocate(在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 rawDataOptional = readBlocking(); rawDataOptional.ifPresent(string -> threadPool.execute(() -> handle(string))); 

这将为您提供一种在套接字(或任何InputStream realy)上读取任何形状或forms的字节数组的方法。 希望这可以帮助!