web-dev-qa-db-fra.com

Java - Comment lire un nombre d'octets inconnu à partir d'un inputStream (socket/socketServer)?

Vous cherchez à lire quelques octets sur un socket en utilisant un inputStream. Les octets envoyés par le serveur peuvent être de quantité variable et le client ne sait pas à l'avance la longueur du tableau d'octets. Comment cela peut-il être accompli?


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

Cela provoque une «erreur peut ne pas être initialisée» du Net BzEAnSZ. Aidez-moi.

14
farm ostrich

Read int, qui correspond à la taille du prochain segment de données en cours de réception. Créez un tampon de cette taille ou utilisez un tampon spacieux et préexistant. Lisez dans la mémoire tampon, en vous assurant qu'elle est limitée à la taille susmentionnée. Rincer et répéter :)

Si vous vraiment ne connaissez pas la taille à l'avance, comme vous l'avez dit, lisez le contenu de ByteArrayOutputStream en expansion, comme indiqué dans les autres réponses. Cependant, la méthode de la taille est vraiment la plus fiable.

11
Chris Dennett

Vous devez développer le tampon au besoin , en lisant des blocs d'octets, 1024 à la fois, comme dans cet exemple de code que j'ai écrit il y a quelque temps.

    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

En supposant que l'expéditeur ferme le flux à la fin des données:

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
Vladimir Dyuzhev

La réponse simple est:

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

BIG_ENOUGH est assez grand.


Mais en général, cela pose un gros problème. La méthode read est non garantie pour renvoyer tout ce que l'autre extrémité a écrit en un seul appel.

  • Si la valeur nosRead est BIG_ENOUGH, votre application n'a aucun moyen de savoir avec certitude s'il reste d'autres octets à venir. l'autre extrémité peut avoir envoyé exactement BIG_ENOUGH octets ... ou plus de BIG_ENOUGH octets. Dans le premier cas, votre application bloquera (pour toujours) si vous essayez de lire. Dans ce dernier cas, votre application doit effectuer (au moins) une autre read pour obtenir le reste des données.

  • Si la valeur nosRead est inférieure à BIG_ENOUGH, votre application still ne le sait pas. Il a peut-être reçu tout ce qui existe, une partie des données a peut-être été retardée (en raison de la fragmentation des paquets sur le réseau, de la perte de paquets sur le réseau, de la partition réseau, etc.), ou l’autre extrémité peut s’être bloquée ou bloquée à mi-chemin de l’envoi des données.

La meilleure réponse est que votre application doit savoir à l'avance combien d'octets sont attendus, ou le "protocole d'application" doit en quelque sorte indiquer le nombre d'octets à attendre ... ou le moment où tous les octets ont été envoyés. Les approches possibles sont:

  • le protocole d'application utilise des tailles de message fixes
  • les tailles de message de protocole d'application sont spécifiées dans les en-têtes de message
  • le protocole d'application utilise des marqueurs de fin de message
  • le protocole d'application n'est pas basé sur un message et l'autre extrémité ferme la connexion pour dire "c'est la fin".

Sans l'une de ces stratégies, votre application est laissée à deviner et risque de se tromper de temps en temps.

9
Stephen C

Sans réinventer la roue, utilisez Apache Commons:

IOUtils.toByteArray(inputStream);

Par exemple, complétez le code avec la gestion des erreurs:

    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 est une exception significative RT spécifique à votre application qui sera transmise sans interruption à votre gestionnaire approprié sans polluer le code entre les deux.

6
constv

Diffusez toutes les données d'entrée dans le flux de sortie. Voici un exemple de travail:

    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

Cette question a 7 ans, mais j'ai eu un problème similaire, tout en créant un système compatible NIO et OIO (le client et le serveur peuvent être ce qu'ils veulent, OIO ou NIO).

Cela a été difficile, à cause du blocage des InputStreams.

J'ai trouvé un moyen, qui le rend possible et que je veuille poster, d'aider les personnes ayant des problèmes similaires.

La lecture d’un tableau d’octets de sice dynamique s’effectue ici avec le DataInputStream , qui peut simplement être encapsulé autour du socketInputStream. De plus, je ne veux pas introduire un protocole de communication spécifique (comme envoyer d’abord la taille en octets, qui sera envoyé), parce que je veux que ce soit aussi vanillé que possible. Tout d'abord, j'ai une classe de tampon utilitaire simple, qui ressemble à ceci:

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

Cette classe n’existe que, à cause de la manière idiote, octet <=> La mise à jour automatique des octets en Java fonctionne avec cette liste. Ce n'est pas vraiment nécessaire dans cet exemple, mais je ne voulais pas laisser quelque chose en dehors de cette explication. 

Ensuite, les 2 méthodes simples et fondamentales. Dans ceux-ci, un StringBuilder est utilisé comme un "rappel". Il sera rempli avec le résultat qui a été lu et le nombre d'octets lus sera retourné. Cela pourrait être fait différemment, bien sûr.

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

La première méthode readNext remplira le tampon, avec byte[] à partir de DataInputStream et renverra le nombre d'octets lus de cette façon.

Dans la seconde méthode, readBlocking, j’ai utilisé la nature bloquante pour ne pas nous préoccuper des problèmes consommateur-producteur- . Simplement readBlocking va bloquer jusqu'à la réception d'un nouveau tableau d'octets. Avant d'appeler cette méthode de blocage, nous allouons une taille de tampon. Remarque, j'ai appelé réaffecter après la première lecture (à l'intérieur de la boucle while). Ce n'est pas nécessaire. Vous pouvez supprimer cette ligne en toute sécurité et le code fonctionnera toujours. Je l'ai fait, à cause de l'unicité de mon problème.

Les 2 choses que je n’ai pas expliquées plus en détail sont: 1. in (DataInputStream et le seul résumé disponible ici, désolé pour cela) 2. déconnecter (votre routine de déconnexion)

Dans l’ensemble, vous pouvez maintenant l’utiliser de cette façon:

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

Cela vous fournira un moyen de lire des tableaux d'octets de n'importe quelle forme sur un socket (ou n'importe quel InputStream réellement). J'espère que cela t'aides!

0
Thorben Kuck

Voici un exemple plus simple utilisant 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");

Cependant, si le serveur ne renvoie pas -1, vous devrez détecter la fin des données d’une autre manière - par exemple, le contenu renvoyé se termine toujours par un certain marqueur (par exemple, ""), ou vous pouvez éventuellement résoudre le problème. en utilisant socket.setSoTimeout (). (Mentionner ceci car cela semble être un problème courant.)

0
ban-geoengineering

Non plus:

  1. Demandez à l'expéditeur de fermer le socket après avoir transféré les octets. Ensuite, au récepteur, continuez à lire jusqu'à EOS.

  2. Demandez à l'expéditeur de préfixer une longueur de Word selon la suggestion de Chris, puis lisez autant d'octets.

  3. Utilisez un protocole auto-descriptif tel que XML, Serialization, ...

0
user207421

C’est à la fois une réponse tardive et une auto-publicité, mais toute personne vérifiant cette question voudra peut-être jeter un coup d’œil ici: https://github.com/GregoryConrad/SmartSocket

0
Flare Cat

Utilisez BufferedInputStream et la méthode available() qui renvoie la taille en octets disponibles pour la lecture, puis créez un byte[] avec cette taille. Problème résolu. :) 

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