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.
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.
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;
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();
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:
Ohne eine dieser Strategien bleibt Ihre Anwendung zu erraten und kann gelegentlich falsch liegen.
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.
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();
}
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!
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.)
Entweder:
Bitten Sie den Sender, den Socket nach der Übertragung der Bytes zu schließen. Dann am Empfänger einfach weiterlesen bis EOS.
Lassen Sie den Absender nach Chris 'Vorschlag ein Längenwort voranstellen und lesen Sie dann so viele Bytes.
Verwenden Sie ein selbstbeschreibendes Protokoll wie XML, Serialisierung, ...
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
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();