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.
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.
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;
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();
La réponse simple est:
byte b[] = byte[BIG_ENOUGH];
int nosRead = sock.getInputStream().read(b);
où 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:
Sans l'une de ces stratégies, votre application est laissée à deviner et risque de se tromper de temps en temps.
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);
}
}
Où 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.
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();
}
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!
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.)
Non plus:
Demandez à l'expéditeur de fermer le socket après avoir transféré les octets. Ensuite, au récepteur, continuez à lire jusqu'à EOS.
Demandez à l'expéditeur de préfixer une longueur de Word selon la suggestion de Chris, puis lisez autant d'octets.
Utilisez un protocole auto-descriptif tel que XML, Serialization, ...
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
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();