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

far*_*ich 14 java sockets byte inputstream

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


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

这会导致Net BzEAnSZ出现"可能未初始化错误".救命.

d-l*_*ive 24

你需要扩大缓冲区根据需要,通过一次以字节为单位,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;
Run Code Online (Sandbox Code Playgroud)

  • 如果网络饱和(即 k 很小),重新分配可能无效。实际上,即使在每个周期重新分配 k=1024 也是昂贵的。我会预先分配一个更大的块(通常建议的当前大小的两倍)并在其中保留当前位置的偏移量。 (2认同)
  • 我解决的问题是缓冲区分配和扩展策略.解决任何非常具体的性能问题绝不是一件好事.我的代码是读取小文件,大多数低于1kb,因此这个数字对我来说很有意义.通常,有效的选择是在服务器端会话特定代码中传输的"通常/平均"字节,其中并发会话可能会阻塞可用内存(如果此类缓冲区异常大).在其他情况下,双重理念也可能效果很好 - 所以这一切都取决于具体情况. (2认同)
  • 嘿,放轻松,不要把它当作个人!:)确实,这是一个可靠的代码. (2认同)

Vla*_*hev 14

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

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();
Run Code Online (Sandbox Code Playgroud)

  • 假设发送方没有关闭流,如果没有更多字节*可用*,该方法将阻止.但答案让我朝着正确的方向前进. (2认同)

Chr*_*ett 11

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

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

  • 请记住,必须验证从远程传递的大小是否合理.恶意用户或远程软件中的错误可能会导致您为缓冲区分配1G的RAM,并立即进入OOM. (3认同)

Ste*_*n C 8

简单的答案是:

byte b[] = byte[BIG_ENOUGH];
int nosRead = sock.getInputStream().read(b);
Run Code Online (Sandbox Code Playgroud)

哪里BIG_ENOUGH足够大.


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

  • 如果nosRead值是BIG_ENOUGH,您的应用程序无法确定是否有更多字节要来; 另一端可能已发送完全BIG_ENOUGH字节...或多于BIG_ENOUGH字节.在前一种情况下,如果您尝试阅读,您的应用程序将阻止(永远).在后一种情况下,您的应用程序必须(至少)执行另一个应用程序read以获取其余数据.

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

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

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

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


小智 6

Without re-inventing the wheel, using Apache Commons:

IOUtils.toByteArray(inputStream);
Run Code Online (Sandbox Code Playgroud)

For example, complete code with 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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Where FileProcessingException is your app-specific meaningful RT exception that will travel uninterrupted to your proper handler w/o polluting the code in between.