Java - Как прочитать неизвестное количество байтов из inputStream (socket / socketServer)? - PullRequest
14 голосов
/ 17 апреля 2011

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

<code>
byte b[]; 
sock.getInputStream().read(b);

Это приводит к тому, что «Net BzEAnSZ» может не инициализировать ошибку.Помощь.

Ответы [ 11 ]

24 голосов
/ 17 апреля 2011

Вам необходимо расширить буфер по мере необходимости , считывая по 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;
13 голосов
/ 17 апреля 2011

Предполагается, что отправитель закрывает поток в конце данных:

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();
11 голосов
/ 17 апреля 2011

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

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

8 голосов
/ 17 апреля 2011

Простой ответ:

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

Лучший ответ: EITHER ваше приложение должно заранее знать, сколько байтов ожидать, ИЛИ протокол приложения должен каким-то образом сообщите приложению, сколько байтов ожидать или когда все байты были отправлены.

Возможные подходы:

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

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

Затем вы используете несколько вызовов чтения и (возможно) несколько буферов.

6 голосов
/ 10 сентября 2013

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

1 голос
/ 22 июня 2012

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

    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();
    }
0 голосов
/ 11 мая 2018

Этому вопросу 7 лет, но у меня была похожая проблема при создании NIO и OIO-совместимой системы (клиент и сервер могут быть любыми, OIO или NIO).

Это было выходом из-за блокировки InputStreams.

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

Чтение байтового массива динамических 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 заполнит буфер с byte[] из DataInputStream и вернет количество байтов, прочитанных таким образом.

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

2 вещи, которые я не объяснил более подробно: 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)));

Это предоставит вам способ чтения байтовых массивов любой формы или формы через сокет (или любую реальность InputStream). Надеюсь, это поможет!

0 голосов
/ 30 апреля 2017

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

0 голосов
/ 13 июня 2013

Вот более простой пример использования 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 (). (Упоминание об этом, как кажется, является распространенной проблемой.)

0 голосов
/ 24 мая 2011

Используйте BufferedInputStream и метод available(), который возвращает размер байтов, доступных для чтения, а затем создайте byte[] с этим размером.Задача решена.:)

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