# Mehrere Datei per DataInput/OutputStream über Socket



## oulegulas (22. Jul 2009)

Hallo alle zusammen,

sitze gerade an einem Projekt für die Uni und komme irgendwie nicht weiter.

Was will ich machen?
Ich habe ein Verzeichniss mit mehreren kleineren Dateien (PDF). Diese soll der Server einzeln an den Client senden.
Soweit so gut. Das Problem daran ist, dass ich keine Ahnung habe, wie der Client erkennen soll, dass gerade eine neue Datei angekommen ist, da er immer Blöcke zu je 1024 Byte bekommt. Daraus resultieren auf der Client- Seite auch ein Haufen Dateien zu je 1024 Byte.

Zur Verdeutlichung ein wenig Code:

Hier der Teil der Serverkomponente:

```
public void sendSplitedPDFFiles(String splitedDir) {
                File splitedPDFDir = new File(splitedDir);
                String[] slideFiles = splitedPDFDir.list(new PDFFilenameFilter());
                for (int i = 0; i < slideFiles.length; i++) {
                        try {
                                log.trace(slideFiles[i]);
                                fileIn = new FileInputStream(splitedPDFDir.getAbsolutePath()
                                + System.getProperty("file.separator") + slideFiles[i]);
                                while (fileIn.available() > 0) {
                                        stringBuffer.append(fileIn.read(buffer));
                                        pdfOutStream.write(buffer, 0, fileIn.read(buffer));
                                }
                                fileIn.close();
                        } catch (FileNotFoundException e) {
                                log.error("Could not find the given file. " + e.getMessage());
                        } catch (IOException e) {
                                log.error("Could not send the file: slide_" + (i + 1) + ".pdf "
                                + e.getMessage());
                        }
                }
        }
```

Dazu noch die Methode zum empfangen: 

```
public void receivePDFFiles(File dir) {
                log.trace("receive pdf files save it to " + dir.getAbsolutePath());
                System.out.println(dir.list().length);
                try {
                        while (true) {
                                log.trace("try to receive");
                                int bytesRead;
                                bytesRead = pdfInStream.read(buffer);
                                if (bytesRead == -1) {
                                        break;
                                }
                                pdfFileOut.write(buffer, 0, bytesRead);
                                pdfFileOut.close();
                        }

                } catch (IOException e) {
                        log.error("Error while reading from stream. " + e.getMessage());
                }
        }
```

Ich wäre sehr verbunden um Hinweise, die mir helfen, dieses Problem zu lösen 

Beste Grüße und vielen Dank im Vorraus,

Martin


----------



## sparrow (23. Jul 2009)

Da musst du, wennd du so vorgehen willst, ein eigenes Protokoll entwickeln.

Eine Möglichkeit wäre:

Du möchtest eine Datei Senden die 41982 Bytes hat

1Byte (Länge der Bytes für die Größe der Datei) (in unserem Fall also 5)
5Byte (Gibt die Länge der eigentlichen Datei an, also: 4 1 9 8 2)
41982 Bytes Datei

Der Client kann das entsprechend auswerten und wegspeichern.
Das lässt sich natürlich noch optimieren und verfeinern. Inkl. Dateinamen, etc.


----------



## Ebenius (23. Jul 2009)

sparrow hat gesagt.:


> Du möchtest eine Datei Senden die 41982 Bytes hat
> 
> 1Byte (Länge der Bytes für die Größe der Datei) (in unserem Fall also 5)
> 5Byte (Gibt die Länge der eigentlichen Datei an, also: 4 1 9 8 2)


Dann schlage ich allerdings vor, die Länge nicht dezimal aufzuschlüsseln, sondern unsigned binär zu übertragen:

1Byte (Länge der Bytes für die Größe der Datei) (in unserem Fall also 2)
2Byte (Gibt die Länge der eigentlichen Datei an, also: A3 FE)
41982 Bytes Daten

Ebenius


----------



## oulegulas (23. Jul 2009)

Ich habe mal versucht die Größe mitzuschicken. Dies geschieht auf folgendem Weg:

```
pdfOutStream.writeLong(new File("pfad").length());
```

Auf der Client- Seite lese ich das ganze dann wieder ein:


```
long i = pdfInStream.readLong();
byte[] buff = new byte[Integer.parseInt(Long.toString(i))];
log.trace("Read INT " + i + " " + Long.toString(i) + " "
	+ Integer.parseInt(Long.toString(i)));
log.trace("try to receive");
int bytesRead;
bytesRead = pdfInStream.read(buff);
```

Auf Client- Seite fliegt mir folgende Exception um die Ohren:

```
13:34:13,887 TRACE ClientPDFThread:68 - Read INT 19728 19728 19728
13:34:13,887 TRACE ClientPDFThread:70 - try to receive
Exception in thread "Thread-3" java.lang.NumberFormatException: For input string: "948389091815964349"
	at java.lang.NumberFormatException.forInputString(NumberFormatException.java:48)
	at java.lang.Integer.parseInt(Integer.java:459)
	at java.lang.Integer.parseInt(Integer.java:497)
	at de.beeld.network.ClientPDFThread.receivePDFFiles(ClientPDFThread.java:67)
	at de.beeld.network.ClientPDFThread.run(ClientPDFThread.java:86)
```

Der Server weigert sich mit der Meldung:

```
Exception in thread "Thread-5" java.lang.ArrayIndexOutOfBoundsException
```

Irgendwie fühl ich mich überfordert :toll:


----------



## sparrow (23. Jul 2009)

Ich werde aus deinem Code nich schlau, weil es immer nur Ausschnitte sind.
Der Fehler ist klar: der Client versucht etwas auf INT zu parsen was nicht in den Wertebereich passt.

Schreib doch den Code für die Übertragung in eine extra Klasse oder Methode, dann ist das von dem Rest getrennt und kann hier auch übersichtlich am Stück gezeigt werden.

Du solltest das ganze sequentiell abarbeiten, wie bei Netzwerkprotokollen üblich.
Der Server sendet nacheinander, wie open von Ebenius beschrieben, und der Client wertet das nacheinander aus.
Client prüft das 1. Byte, erkennt wieviele der nächstn Bytes die Gesamtlänge der Datei angeben. Liest die nächsten Bytes um die Größe zu ermitteln. Liest danach die Anzahl von Bytes und schreibt die in eine Datei die er mitgeteilt bekommen hat.


----------



## oulegulas (27. Jul 2009)

Der komplette Code wäre einfach zu viel für diesen Thread, deswegen habe ich ihn nicht gepostet. Prinzipiell ist der Code über Sourceforge erreichbar, bei Interesse einfach PM.

Ich habe es jetzt soweit hinbekommen, das Senden und Empfangen über mein vorhandenes Protokoll zu steuern.

Allerdings kann ich nicht immernoch nicht herausfinden wann eine Datei zu Ende ist.

Daher nochmals ein paar Schnipsel, in der Hoffnung, dass sie doch etwas Aussagekraft haben.

Der Teil zum senden; Eine Datei öffnen und so lange einlesen, bis es nichts mehr gibt und in den OutStream schreiben.

```
log.trace("Current file I try to send: "
	+ currentFile.getAbsolutePath());
fileIn = new FileInputStream(currentFile);
while (fileIn.available() > 0) {
	pdfOutStream.write(buffer, 0, fileIn.read(buffer));
}
fileIn.close();
```


Der Teil zum empfangen; solange 1024 Byte einlesen, bis -1 kommt. Nur leider kommt kein -1. Irgendwie; Und ich weiß nicht wie ich sonst überprüfen soll, dass das lesen aus dem InStream vorbei ist...

```
while (loop) {
	int bytesRead;
	bytesRead = pdfInStream.read(buffer);
	totallyBytesRead = totallyBytesRead + bytesRead;
	log.trace("Currently read: " + bytesRead);
	if (bytesRead == -1) {
		log.info("Finished transfer of " + slideName + " and read "
			+ totallyBytesRead + " bytes for this file.");
		pdfFileOut.close();
		Client.getInstance().sendMessage(NetworkMessages.PROCEED,
							slideName);
		loop = false;
	}
	pdfFileOut.write(buffer, 0, bytesRead);
	log.trace("Read " + totallyBytesRead + " of current slide "
		+ slideName);
}
```


----------



## sparrow (27. Jul 2009)

Moment, ein -1 kommt erst wenn du von Serverseite die Verbindung schließt. Das willst du ja nicht, du willst ja nacheinander die Dateien senden.
Woran scheitert es denn genau, bzw. was hast du von den Vorschlägen hier im Thread schon umgesetzt? Überträgst du die Größe der Datei vor der eigentlichen Datei? Weiß der Client wieviele Bytes er zu erwarten hat?


----------



## oulegulas (27. Jul 2009)

Ich habe versucht, den Buffer so groß wie die Größe zu machen, aber da kam eine NumberFormatException.

Ich denke, da war irgendwie der Cast von Long (welches du bekommst, wenn du File.length() benutzt) nach int (was du brauchst, wenn du den Buffer anlegst buffer = new byte[filesize].

Das Problem ist eben momentan, wie ich herausfinde, dass die Datei fertig übertragen ist.
Gelöst habe ich es  (recht unschön ehrlich gesagt), dass ich schaue, ob der gelesene Buffer kleiner als 1024 ist.

```
while (loop) {
	int bytesRead;
	bytesRead = pdfInStream.read(buffer);
	totallyBytesRead = totallyBytesRead + bytesRead;
	log.trace("Currently read: " + bytesRead);
	if (bytesRead != 1024) {
		pdfFileOut.write(buffer, 0, bytesRead);
		log.info("Finished transfer of " + slideName + " and read "
			+ totallyBytesRead + " bytes for this file.");
		pdfFileOut.close();
		Client.getInstance().sendMessage(NetworkMessages.PROCEED,
			slideName);
		loop = false;
	}
	pdfFileOut.write(buffer, 0, bytesRead);
	log.trace("Read " + totallyBytesRead + " of current slide "
		+ slideName);
}
```


----------



## sparrow (27. Jul 2009)

Das funktioniert so nicht.

Fühl dich jetzt bitte nicht verscheißert, aber mir fällt es immer einfacher so etwas zu verstehen wenn ich das bildlich erklärt kriege.

Ich versuch dir das mal auf eine einfache Weise zu erklären. Wenn du zwischen 2 PCs eine TCP/IP-Verbindung aufbaust, dann ist das für dich schonmal ein ziemlich simples Protokoll. Im Prinzip eine Straße mit einer Fahrbahn von A nach B und eine Fahrbahn von B nach A. Warum ist das für dich simpel? Weil du dich um die Übertragung an sich nicht kümmern musst. Du setzt ein Auto nach dem anderen auf eine von den beiden Spuren und schon fährt das zu dem anderen PC. Das die sich nicht gegenseitig überholen bzw. der ADAC ein Auto wieder flott macht wenn es liegen geblieben ist, darum kümmert sich bereits das Protokoll für dich.
Sobald die Straße erst mal da ist funktioniert das alles sehr gut. Beide Seiten können sogar reagieren wenn die andere Seite beschließt die Verbindung abzubauen und die Straße quasi abzureißen (eine Ausnahme ist hier das unerwartete Ende durch so etwas wie Stecker ziehen, aber das lassen wir hier mal außen vor).

So, du hast dir nun eine Puffer geschrieben, so eine Art Fähre. 1024 Autos gehen in die Fähre rein. Es kommen 960. Fehlen noch 64. Was passiert? Die Fähre fährt gar nicht los weil sie nicht voll ist. Das ist auch gut so. Es kann nämlich auch sein, dass son Opa mit Wackeldackel den ganzen Verkehr aufhällt und in Wirklichkeit noch viele, viele Autos fehlen.
Der Server könnte die Verbindung schließen, das würde der Client merken, allerdings ist das ja nicht das was du erreichen wolltest.

Den Lösungsweg habe ich oben schon beschrieben.
Du lässt 1 Auto bei A losfahren das anzeigt wieviele der nächsten Autos (in diesem Beispiel 3) die Gesamtgröße der Daten angeben.
B empfängt das Auto und sieht, aha die nächsten 3 Autos enthalten die Gesamtzahl der zu erwartendenden Dateidatenautos. (Hier könnte B ein 'OK schick los' antworten)
A lässt 3 Autos losfahren die die Information tragen wieviele Autos für die Datendatei zu erwarten sind (sagen wir 12859).
B empfängt diese 3 Autos (hier muss natürlich migezählt werden), wertet den Wert aus und weiß nun, dass die nächsten 12859 Autos Dateidatenautos sind. (Hier könnte B ein 'OK schick los' antworten)
A schickt nun 12859 Autos auf den Weg.
B zählt die Autos mit und weiß nach 12859 Autos, dass die Dateiübertragung nun beendet ist. Wenn der Server anschließend die Verbindung schließt kommt definitiv nichts mehr, ansonsten könnte B auf das nächste Auto warten das ihm sagt wieviele der nächsten Autos die Menge der Dateidatenautos enthalten und es geht von vorne los...


----------



## oulegulas (27. Jul 2009)

Ich fühle mich nicht verscheißert, im Gegenteil danke ich dir, dass du versuchst, es mir so einfach zu erklären.

Das es nicht funktioniert, is nicht richtig, es funktioniert schon. Die Dateien werden ohne jegliche Probleme übertragen, es fliegt lediglich am Ende eine IOException. Dh, er überträgt auch einen Buffer, der kleiner ist als 1024 (zB 96 oder 243 Byte).

Aber ich habe durch deine nette Erklärung noch eine Idee bekommen, wie ich das umgehen könnte. Mal probieren


----------



## oulegulas (27. Jul 2009)

Ich fühle mich nicht verscheißert, im Gegenteil danke ich dir, dass du versuchst, es mir so einfach zu erklären.

Das es nicht funktioniert, is nicht richtig, es funktioniert schon. Die Dateien werden ohne jegliche Probleme übertragen, es fliegt lediglich am Ende eine IOException. Dh, er überträgt auch einen Buffer, der kleiner ist als 1024 (zB 96 oder 243 Byte).

Aber ich habe durch deine nette Erklärung noch eine Idee bekommen, wie ich das umgehen könnte. Mal probieren


----------



## sparrow (27. Jul 2009)

Das freut mich.

Und wenns nicht geht nehmen wir hier mal deinen Code Zeile für Zeile auseinander 
Aber bitte mit der Übertragung der Größe vorweg, mir fällt nämlich keine andere Möglichkeit ein.


----------



## oulegulas (27. Jul 2009)

Also gelöst...

Ich hab die Dateigröße in meinen Paketen mitgeschickt, und schaue nun einfach, ob ich schon so viele Bytes gelesen habe, wie die Datei groß sein sollte 


```
try {
	while (loop) {
		int bytesRead;
		bytesRead = pdfInStream.read(buffer);
		totallyBytesRead = totallyBytesRead + bytesRead;
		pdfFileOut.write(buffer, 0, bytesRead);
		if (totallyBytesRead == Integer.parseInt(Long.toString(fileSize))) {
			loop = false;
		}
	}
}
```

Großen Dank für die nette Hilfe


----------

