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

时间:2011-04-17 01:56:17

标签: java sockets byte inputstream

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


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

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

11 个答案:

答案 0 :(得分: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;

答案 1 :(得分:13)

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

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

答案 2 :(得分:11)

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

如果确实未按照您的说法提前知道大小,请阅读扩展的ByteArrayOutputStream,如其他答案所述。但是,尺寸方法确实是最可靠的。

答案 3 :(得分:8)

简单的答案是:

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,您的应用仍然不知道。它可能已收到所有内容,部分数据可能已被延迟(由于网络数据包碎片,网络数据包丢失,网络分区等),或者另一端可能已通过发送数据而部分阻塞或崩溃。 / p>

最好的答案是 EITHER 您的应用程序需要事先知道需要多少字节, OR 应用程序协议需要以某种方式告诉应用程序需要多少字节或何时发送所有字节。

可能的方法是:

  • 应用程序协议使用固定的邮件大小(不适用于您的示例)
  • 应用程序协议邮件大小在邮件头
  • 中指定
  • 应用程序协议使用消息结束标记
  • 应用程序协议不是基于消息的,而另一端关闭连接以说明即结束

如果没有其中一种策略,您的应用程序就会被猜测,偶尔也会出错。

然后你使用多个读取调用和(可能)多个缓冲区。

答案 4 :(得分:6)

没有重新发明轮子,使用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异常,它将不间断地传递给您正确的处理程序,而不会污染其间的代码。

答案 5 :(得分:1)

将所有输入数据流式传输到输出流中。这是一个有效的例子:

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

答案 6 :(得分:0)

或者:

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

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

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

答案 7 :(得分:0)

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

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

答案 8 :(得分:0)

这是一个使用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()。 (提及这似乎是一个常见的问题。)

答案 9 :(得分:0)

这是一个迟到的答案和自我广告,但是任何查看此问题的人都可以在这里查看: https://github.com/GregoryConrad/SmartSocket

答案 10 :(得分:0)

这个问题已经有7年了,但我有一个类似的问题,同时制作一个NIO和OIO兼容系统(客户端和服务器可能是他们想要的任何东西,OIO或NIO)。

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

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

使用DataInputStream读取动态sice的字节数组,其中kann只是包裹在socketInputStream中。另外,我不想引入特定的通信协议1(比如首先发送将要发送的字节大小),因为我想尽可能地将其作为vanilla。首先,我有一个简单的实用程序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;
    }
}

这个类只存在,因为愚蠢的方式,字节&lt; =&gt; Java中的字节自动装箱与此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<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[]填充缓冲区,并返回以这种方式读取的字节数。

在secon方法readBlocking中,我利用了阻止性质,而不用担心consumer-producer-problems。简单地readBlocking将阻塞,直到接收到新的字节数组。在我们调用这个阻塞方法之前,我们分配一个Buffer-size。注意,我在第一次读取后调用reallocate(在while循环内)。这不是必需的。您可以安全地删除此行,代码仍然有效。我做到了,因为我的问题是独一无二的。

我没有详细解释的两件事是:  1. in(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)));

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