Java. Как читать неизвестное количество байтов из inputStream (socket/socketServer)?
Глядя на чтение в некоторых байтах через сокет, используя inputStream. Байты, отправленные сервером, могут иметь переменную величину, и клиент не знает заранее длину массива байтов. Как это может быть достигнуто?
byte b[];
sock.getInputStream().read(b);
Это приводит к тому, что ошибка "не может быть инициализирована" из Net BzEAnSZ. Справка.
Ответы
Ответ 1
Прочитайте значение int, которое представляет собой размер следующего сегмента полученных данных. Создайте буфер с этим размером или используйте вместительный ранее существующий буфер. Прочитайте в буфер, убедившись, что он ограничен размером aforeread. Промыть и повторить:)
Если вы действительно не знаете размер заранее, как вы сказали, прочитайте расширенный ByteArrayOutputStream, как упомянули другие ответы. Однако метод размера действительно является самым надежным.
Ответ 2
Вам нужно развернуть буфер по мере необходимости, читая куски байтов, 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;
Ответ 3
Предполагая, что отправитель закрывает поток в конце данных:
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();
Ответ 4
Простой ответ:
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
, ваше приложение все еще не знает. Возможно, он получил все, что есть, часть данных могла быть отложена (из-за фрагментации сетевого пакета, потери сетевого пакета, сетевого раздела и т.д.), Или другой конец мог заблокировать или потерпел неудачу частично во время отправки данных.
Лучший ответ: ЛИБО ваше приложение должно заранее знать, сколько байтов ожидать, ИЛИ протокол приложения должен каким-то образом сообщить приложению, сколько байтов ожидать или когда все байты были отправлены.
Возможные подходы:
- протокол приложения использует фиксированные размеры сообщений (не относится к вашему примеру)
- размеры сообщений протокола приложения указаны в заголовках сообщений
- протокол приложения использует маркеры конца сообщения
- протокол приложения не основан на сообщениях, а другой конец закрывает соединение, чтобы сказать, что это конец.
Без одной из этих стратегий ваше приложение остается угаданным и может иногда ошибаться.
Ответ 5
Без повторного создания колеса, используя 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, специфичное для вашего приложения, которое будет беспрепятственно перемещаться к вашему надлежащему обработчику без загрязнения кода между ними.
Ответ 6
Поток всех входных данных в выходной поток. Вот рабочий пример:
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();
}
Ответ 7
Или:
-
Откажите отправителю закрыть сокет после передачи байтов. Затем в приемнике просто продолжайте читать до EOS.
-
Попросите отправителя префикс слова длины в соответствии с предложением Криса, затем прочитайте, что много байтов.
-
Используйте самоописывающий протокол, такой как XML, Serialization,...
Ответ 8
Используйте BufferedInputStream
и используйте метод available()
, который возвращает размер доступных для чтения байтов, а затем создайте byte[]
с этим размером. Задача решена.:)
BufferedInputStream buf = new BufferedInputStream(is);
int size = buf.available();
Ответ 9
Вот более простой пример использования 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(). (Упоминание об этом, поскольку это, кажется, общая проблема.)
Ответ 10
Это как поздний ответ, так и самореклама, но любой, кто проверяет этот вопрос, может захотеть посмотреть здесь:
https://github.com/GregoryConrad/SmartSocket
Ответ 11
Этому вопросу 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. in (DataInputStream и единственное короткое переменное здесь, извините за это) 2. connect (ваша процедура отключения)
В общем, теперь вы можете использовать его следующим образом:
// 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). Надеюсь это поможет!