Java - как прочитать неизвестное количество байтов из inputStream (socket / socketServer)?

глядя на чтение в некоторых байтах через сокет с помощью inputStream. Байты, отправленные сервером, могут иметь переменное количество, и клиент заранее не знает длину массива байтов. Как это можно сделать?


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

это вызывает "не может быть инициализирована ошибка" из Net BzEAnSZ. Помощь.

11 ответов


прочитайте int, который является размером следующего получаемого сегмента данных. Создайте буфер такого размера или используйте вместительный уже существующий буфер. Читать в буфер, убедившись, что он ограничен размерами aforeread. Промыть и повторить:)

Если вы действительно не знаю размер заранее, как вы сказали, прочитайте в расширяющемся ByteArrayOutputStream, как упоминалось в других ответах. Однако метод size действительно является самым надежным.


необходимо развернуть буфер по мере необходимости, читая в кусках байтов, 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 достаточно большой.


но в целом есть большая проблема с этим. The read метод не гарантируется чтобы вернуть все, что другой конец написал в одном вызове.

  • если nosRead значение BIG_ENOUGH, ваше приложение не может точно знать, есть ли еще байты; другой конец, Возможно, отправил точно BIG_ENOUGH байт ... или больше, чем BIG_ENOUGH байт. В первом случае приложение будет блокировать (навсегда) если вы пытаетесь читать. В последнем случае ваше приложение должно сделать (по крайней мере) другое read чтобы получить остальные данные.

  • если nosRead значение меньше BIG_ENOUGH приложение еще не знает. Возможно, он получил все, что есть, часть данных может быть задержана (из-за фрагментации сетевого пакета, потери сетевого пакета, сетевой раздел и т. д.), Или другой конец, Возможно, заблокировал или разбился на полпути через отправку данных.

лучший ответ заключается в том, что ваше приложение либо должно знать заранее, сколько байтов ожидать, либо "протокол приложения" должен каким-то образом рассказать это сколько байт ждать ... или когда все байты были отправлены. Возможные подходы:

  • протокол приложения использует фиксированное сообщение размеры
  • размеры сообщений протокола приложения указаны в заголовках сообщений
  • протокол приложения использует маркеры конца сообщения
  • протокол приложения не основан на сообщении, а другой конец закрывает соединение, чтобы сказать "это конец".

без одной из этих стратегий ваше приложение остается угадывать и может иногда ошибаться.


без повторного изобретения колеса, используя 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, которое будет непрерывно перемещаться к Вашему правильному обработчику без загрязнения кода между ними.


поток всех входных данных в выходной поток. Вот рабочий пример:

    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. имейте префикс отправителя слово длины согласно предложению Криса, а затем прочитайте, что много байтов.

  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, вам нужно будет определить конец данных каким - то другим способом-например, возможно, возвращаемое содержимое всегда заканчивается определенным маркером (например,""), или вы могли бы решить с помощью сокета.setSoTimeout(). (Упоминание об этом, как представляется, является общей проблемой.)


Это как поздний ответ, так и самореклама, но любой, кто проверяет этот вопрос, может захотеть взглянуть здесь: https://github.com/GregoryConrad/SmartSocket


этому вопросу 7 лет, но у меня была аналогичная проблема, делая НИО и OIO совместимая система (Клиент и сервер могут быть тем, что они хотят, OIO или NIO).

это было бросить вызов, из-за блокировки InputStreams.

Я нашел способ, который делает возможным и я хочу разместить его, чтобы помочь людям с аналогичными проблемами.

чтение массива байтов динамического sice выполняется здесь с помощью DataInputStream, который Канн просто обернут вокруг socketInputStream. Кроме того, я не хочу вводить конкретный протокол связи (например, сначала отправлять размер байтов, который будет отправлен), потому что я хочу сделать это как можно более ванильным. Во-первых, у меня есть простой класс буфера утилиты, который выглядит так:

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

этот класс существует только из-за немого способа, Byte Byte autoboxing в 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 заполнит буфер, с byte[] из DataInputStream и вернуть количество байтов прочитайте это путь.

в методе secon,readBlocking, я использовал блокирующий характер, чтобы не волноваться о потребитель-производитель-проблемы. Просто readBlocking будет блокировать, пока не будет получен новый байт-массив. Перед вызовом этого метода блокировки мы выделяем размер буфера. Обратите внимание, что я вызвал перераспределение после первого чтения (внутри цикла while). Это не требуется. Вы можете удалить эту строку и код все равно будет работать. Я сделал это, из-за уникальности моего проблема.

2 вещи, я не объяснил более подробно: 1. in (DataInputStream и единственный короткий varaible здесь, Извините за это) 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). Надеюсь, это поможет!