wake-up-neo.com

Java - Wie kann ich eine unbekannte Anzahl von Bytes aus einem inputStream (socket/socketServer) lesen?

Einige Bytes über einen Socket mit inputStream einlesen. Die vom Server gesendeten Bytes sind möglicherweise variabel und der Client kennt die Länge des Bytearrays nicht im Voraus. Wie kann das erreicht werden?


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

Dies führt zu einem "möglicherweise nicht initialisierten Fehler" vom Net BzEAnSZ. Hilfe.

14
farm ostrich

Liest ein int, also die Größe des nächsten empfangenen Datensegments. Erstellen Sie einen Puffer mit dieser Größe oder verwenden Sie einen geräumigen, bereits vorhandenen Puffer. Lesen Sie in den Puffer und stellen Sie sicher, dass er auf die oben genannte Größe beschränkt ist. Spülen und wiederholen :)

Wenn Sie wirklich die Größe nicht im Voraus wissen, wie Sie gesagt haben, lesen Sie in einem erweiterten ByteArrayOutputStream nach, wie in den anderen Antworten erwähnt. Die Größenmethode ist jedoch am zuverlässigsten.

11
Chris Dennett

Sie müssen den Puffer bei Bedarf erweitern, indem Sie jeweils 1024 Byte in Byte einlesen, wie in diesem Beispielcode, den ich vor einiger Zeit geschrieben habe

    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;
24
d-live

Angenommen, der Absender schließt den Stream am Ende der Daten:

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

Die einfache Antwort lautet:

byte b[] = byte[BIG_ENOUGH];
int nosRead = sock.getInputStream().read(b);

wo BIG_ENOUGH groß genug ist.


Aber im Allgemeinen gibt es ein großes Problem. Die read-Methode gibt nicht garantiert alle zurück, die das andere Ende in einem einzigen Aufruf geschrieben hat.

  • Wenn der nosRead-Wert BIG_ENOUGH ist, kann Ihre Anwendung nicht sicher wissen, ob noch weitere Bytes vorhanden sind. Das andere Ende hat möglicherweise genau BIG_ENOUGH Bytes ... oder mehr als BIG_ENOUGH Bytes gesendet. Im ersten Fall blockiert Ihre Anwendung (für immer), wenn Sie versuchen, zu lesen. Im letzteren Fall muss Ihre Anwendung (mindestens) eine weitere read ausführen, um die restlichen Daten abzurufen.

  • Wenn der nosRead-Wert kleiner als BIG_ENOUGH ist, weiß Ihre Anwendung still dies nicht. Es hat möglicherweise alles erhalten, was vorhanden ist, ein Teil der Daten wurde möglicherweise verzögert (aufgrund von Netzwerkpaket-Fragmentierung, Netzwerkpaketverlust, Netzwerkpartition usw.), oder das andere Ende ist möglicherweise blockiert oder auf halbem Weg durch das Senden der Daten abgestürzt.

Die beste Antwort ist, dass Ihre Anwendung entweder vorher wissen muss, wie viele Bytes zu erwarten sind, oder das "Anwendungsprotokoll" muss irgendwie tell sagen, wie viele Bytes zu erwarten sind ... oder wann alle Bytes gesendet wurden. Mögliche Ansätze sind:

  • das Anwendungsprotokoll verwendet feste Nachrichtengrößen
  • die Nachrichtengrößen des Anwendungsprotokolls werden in Nachrichtenkopfzeilen angegeben
  • das Anwendungsprotokoll verwendet Nachrichtenende-Marker
  • das Anwendungsprotokoll ist nicht nachrichtenbasiert und das andere Ende schließt die Verbindung und sagt "das ist das Ende".

Ohne eine dieser Strategien bleibt Ihre Anwendung zu erraten und kann gelegentlich falsch liegen.

9
Stephen C

Ohne das Rad neu zu erfinden, verwenden Sie Apache Commons:

IOUtils.toByteArray(inputStream);

Vervollständigen Sie beispielsweise den Code mit der Fehlerbehandlung:

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

Wobei FileProcessingException Ihre app-spezifische sinnvolle RT) Ausnahme ist, die ununterbrochen zu Ihrem richtigen Handler weitergeleitet wird, ohne den Code dazwischen zu verschmutzen.

6
constv

Alle Eingabedaten in den Ausgabestrom streamen. Hier ist ein Arbeitsbeispiel:

    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
DejanR

Diese Frage ist 7 Jahre alt, aber ich hatte ein ähnliches Problem, während ich ein NIO und ein OIO-kompatibles System herstellte (Client und Server können sein, was sie wollen, OIO oder NIO).

Dies wurde aufgrund der blockierenden InputStreams abgebrochen.

Ich habe einen Weg gefunden, der es möglich macht und ich möchte ihn posten, um Menschen mit ähnlichen Problemen zu helfen.

Das Lesen eines Bytearrays von dynamischen Sices erfolgt hier mit dem DataInputStream , der einfach um den socketInputStream gewickelt werden kann. Ich möchte auch kein spezielles Kommunikationsprotokoll einführen (wie das Senden der Bytes in der Größe, die gesendet werden soll), da ich dies so Vanilla wie möglich machen möchte. Zunächst habe ich eine einfache Dienstprogramm-Pufferklasse, die wie folgt aussieht:

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

Diese Klasse existiert nur aufgrund des dummen Bytes <=> Byte Autoboxing in Java funktioniert mit dieser Liste. Dies ist in diesem Beispiel nicht wirklich nötig, aber ich wollte nichts von dieser Erklärung weglassen. 

Als nächstes die 2 einfachen Kernmethoden. In diesen wird ein StringBuilder als "Rückruf" verwendet. Es wird mit dem gelesenen Ergebnis gefüllt und die Anzahl der gelesenen Bytes wird zurückgegeben. Dies kann natürlich anders erfolgen.

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

Die erste Methode readNext füllt den Puffer mit byte[] aus DataInputStream und gibt die auf diese Weise gelesenen Anzahl Bytes zurück.

In der zweiten Methode, readBlocking, habe ich die blockierende Natur ausgenutzt, um mich nicht um Verbraucher-Hersteller-Probleme zu sorgen. Einfach readBlocking wird blockieren, bis ein neues Byte-Array empfangen wird. Bevor wir diese Blockierungsmethode aufrufen, weisen wir eine Puffergröße zu. Ich habe nach dem ersten Lesen (innerhalb der while-Schleife) Reallocate aufgerufen. Das ist nicht nötig. Sie können diese Zeile sicher löschen und der Code funktioniert weiterhin. Ich habe es wegen der Einzigartigkeit meines Problems getan.

Die 2 Dinge, die ich nicht näher erläutert habe, sind: 1. in (der DataInputStream und die einzige kurze varaible hier, sorry dafür) 2. Verbindung trennen (Ihre Verbindungsroutine)

Alles in allem können Sie es jetzt so verwenden:

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

Auf diese Weise können Sie Byte-Arrays beliebiger Form über einen Socket (oder über InputStream) lesen. Hoffe das hilft!

0
Thorben Kuck

Hier ist ein einfacheres Beispiel mit 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");

Wenn der Server jedoch keine -1 zurückgibt, müssen Sie das Ende der Daten auf andere Weise ermitteln. Möglicherweise endet der zurückgegebene Inhalt immer mit einer bestimmten Markierung (z. B. "") using socket.setSoTimeout (). (Das zu erwähnen, wie es ist, scheint ein häufiges Problem zu sein.)

0

Entweder:

  1. Bitten Sie den Sender, den Socket nach der Übertragung der Bytes zu schließen. Dann am Empfänger einfach weiterlesen bis EOS.

  2. Lassen Sie den Absender nach Chris 'Vorschlag ein Längenwort voranstellen und lesen Sie dann so viele Bytes.

  3. Verwenden Sie ein selbstbeschreibendes Protokoll wie XML, Serialisierung, ...

0
user207421

Dies ist sowohl eine späte Antwort als auch eine Eigenwerbung, aber wer diese Frage überprüft, kann hier nachsehen: https://github.com/GregoryConrad/SmartSocket

0
Flare Cat

Verwenden Sie BufferedInputStream und verwenden Sie die available()-Methode, die die Größe der zum Lesen verfügbaren Bytes zurückgibt. Erstellen Sie dann einen byte[] mit dieser Größe. Problem gelöst. :) 

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