# Paralleler Dateitransfer: Ein Client - Mehrere Sockets? Wie connecten?



## boehmi (29. Sep 2009)

Hallo,

ich schreibe gerade an einer Client-Server Anwendung.
Es können mehrere Clients zum Server connecten, welche dann vom Server in mehreren Threads verwaltet werden - sozusagen ein "Kommunikationskanal".

Nun soll der Client die Möglichkeit haben Dateien an den Server zu senden, ohne dass dieser Kommunikationskanal belegt wird, optional sollen auch mehrere Dateien gleichzeitig gesendet werden können.

Das heißt, dass sich auf beiden Seiten am besten wieder ein Thread mit eigenem Socket darum kümmern sollte.

Allerdings weiß ich gerade nicht, wie ich diese beiden Threads miteinander kommunizieren lassen soll? Also wie finden sich die Sockets?

Der Server weißt ja nicht ob es sich beim Eingang einer Connection um eine zur Kommunikation oder zur Dateiübertragung handelt.
Außerdem handelt es sich bei einer IP nicht zwangsläufig um den selben Client.

Könnt ihr mir helfen?
Google fördert leider gar nix 

Vielen Dank


----------



## tuxedo (30. Sep 2009)

Was du brauchst ist ein Protokoll ... 

Mit einem solchen kannst du auf eine Socketverbindung beliebige Daten und Dateien gleichzeitig transportieren. 

Mehrere Socketverbindungen vom Client zum Server sind zwar auch möglich, allerdings hast du dadurch nicht wirklich einen Geschwindigkeitsvorteil.




> Allerdings weiß ich gerade nicht, wie ich diese beiden Threads miteinander kommunizieren lassen soll? Also wie finden sich die Sockets?


Kein Plan was du damit genau meist ...

Vielleicht versuchst du das ganze nochmal etwas genauer auszuführen und ggf. mit einem Codebeispiel (auch Pseudocode) zu unterstreichen?!

- Alex


----------



## boehmi (30. Sep 2009)

tuxedo hat gesagt.:


> Was du brauchst ist ein Protokoll ...




Hallo, ja das habe ich schon ;-)


Ich habe ja probiert, die Datei auf Clientseite einfach mit einem BufferedInputReader auszulesen und mit einer while-Schleife Scheibchenweise mit einem Keyword am Anfang zu übertragen, welche dem Server erkenntlich macht, dass es sich um ein Dateistück zu DownloadID x handelt.

Das Problem ist nur, dass die Konvertierung des ausgelesenen Bytearrays zu String und zurück (vor und nach der Übertragung) scheinbar die Daten kaputt macht.
Jedenfalls werden Zeilenumbrüche scheinbar als Stringende betrachtet.

Weiterhin ist es ja so, dass der Client während er in der while-Schleife (die Dateistücken schickt) steckt, nicht auf Serverkommando's reagiert?

Die Übertragungsgeschwindigkeit war damit auch mieserabel... ca. 2kb/s von localhost zu localhost ;-)


Wie kann ich mehrere Socketverbindungen realisieren?

Der Server listend auf einem Port und für jede eingehende Verbindung erzeugt er einen neuen Socket + Client Thread, der über das Protokoll mit dem Client kommuniziert.
Die Dateiübertragung könnte ja aber prinzipiell in einem Rutsch ohne Protokoll in einem Extra Download-Thread laufen.
Denn der "normale" Server weiß ja aber nicht, ob es sich bei der Verbindungsanfrage um eine Client-Anmeldung oder um einen beginnenden Filetransfer handelt?

Somit weiß ich gerade nicht, woran ich entscheiden soll, ob ich einen Client-Thread oder einen Download-Thread erzeuge.

Gruß


----------



## tuxedo (30. Sep 2009)

boehmi hat gesagt.:


> Hallo, ja das habe ich schon ;-)



Naja, wenn ich so weiterlese dann muss ich dir sagen: Das ist kein Protokoll, das ist ein Zustand :-(



> Ich habe ja probiert, die Datei auf Clientseite einfach mit einem BufferedInputReader auszulesen und mit einer while-Schleife Scheibchenweise mit einem Keyword am Anfang zu übertragen, welche dem Server erkenntlich macht, dass es sich um ein Dateistück zu DownloadID x handelt.
> 
> Das Problem ist nur, dass die Konvertierung des ausgelesenen Bytearrays zu String und zurück (vor und nach der Übertragung) scheinbar die Daten kaputt macht.
> Jedenfalls werden Zeilenumbrüche scheinbar als Stringende betrachtet.



Falsch. Setzen. 6.

Sorry. Wenn du auf einem Stream verschiedene Datentypen verwenden willst, dann solltest du den DataInputStream und den DataOutputStream verwenden. 

Auf beiden Seiten (Server und Client) solltest du einen Empfangs-Thread anlegen der eingehende Nachrichten auswertet  und dann entscheidet was damit zu tun ist. 

Eine Nachricht könnte wie folgt aussehen:

*Header*
1 byte: Typ der Nachricht. Mit einem Byte lassen sich 256 Nachrichtentypen abbilden. 
1 Integer (das sind intern 4 bytes): Länge der restlichen Nachricht
*Body*
x bytes: die Restliche Nachricht, welche wiederumm in abhängigkeit des Nachrichtentyps aus verschiedenen primitoven/komplexen bestehen kann

Was du dann brauchst sind Nachrichten wie

1) "Server, bereite dich für den Empfang von Datei 1 namens XYZ vor"
2) "Server, hier kommt ein Teil der Datei 1"
3) "Server, ich beende mich jetzt"
4) "Client, dein gewünschter Dateitransfer 1 ist okay/nicht okay"

Nachricht 1) könnte wie folgt aufgebaut sein:

*Header*
1. byte: 0x00 
Integer: 22 (komplette Länge des Bodys)
*Body*
Integer: 1 (Nummer/ID der Datei, wird später noch gebraucht, entspricht 4 bytes)
String: MyFile.ext (Name der Datei, entspricht 10 bytes)
long: 120000 (Die Datei hat soviele bytes ..., ein long ist 8 byte lang)

Und hier kommt Nachricht-Typ 2)

*Header*
1. byte: 0x01 
Integer: 1028 (komplette Länge des Bodys)
*Body*
Integer: 1 (Nummer/ID der Datei, wird später noch gebraucht, entspricht 4 bytes)
byte[]: Ein erster Datenblock der Datei, z.B. 1024 bytes 

Der Server hat ja mit der Nachricht vom Typ 0x00 schon mitbekommen wie lang die Datei ist und weiß wann er aufhören muss die Datei auf die Platte zu schreiben.

So, nun zu nachricht 3)


*Header*
1. byte: 0x02
Integer: 0 (komplette Länge des Bodys)
*Body*
kein body notwendig ... Dient ja nur der Signalisierung dass der Client nun weg ist..

Und Nachricht 4)

*Header*
1. byte: 0x03
Integer: 5 (komplette Länge des Bodys)
*Body*
Integer: 1 (Nummer/ID der betreffenden Datei, entspricht 4 bytes)
byte: 0x00 steht für OKAY, 0xFF steht für NICHT OKAY (entspricht 1 byte länge)

Mit diesen Nachrichten kannst du parallel mehrere Dateien gleichzeitig über eine Socketverbindung übertragen. DU hast zwar "etwas" overhead durch das Protokoll, aber das bisschen zusätzliche Last beim schrieben einen Datenpaketes (lediglich 12 bytes ...) fällt heutzutage absolut nicht ins Gewicht.



> Weiterhin ist es ja so, dass der Client während er in der while-Schleife (die Dateistücken schickt) steckt, nicht auf Serverkommando's reagiert?



Wie gesagt. Mit einem vernünftigen Protokoll und je einem Thread pro Socket-Seite kannst du prüfen was für eine Nachtricht kommt und diese ggf. an andere Threads zur Verarbeitung/speicherung weiterleiten.  Mit deinem bisheirgen Protokoll-Versuch geht das nicht wirklich. Das war/ist zu rudimentär. 



> Die Übertragungsgeschwindigkeit war damit auch mieserabel... ca. 2kb/s von localhost zu localhost ;-)



Naja, man sollte ja auch nciht versuchen Dateien als Strings zu übertragen. Wenn du den Versuch abgeschafft hast wirst du sehen das es schon ziemlich schnell geht. Schneller als deine Internetverbindung es zulässt. Und wenn du den Nagle-Algo auf dem Socket noch abschaltest, den Puffer entsprechend setzt und die Datenpakete die dazu passende Größe haben wirst du auch ein 100mbit Netzwerk gut ausgelastet bekommen.

- Alex


----------



## boehmi (30. Sep 2009)

tuxedo hat gesagt.:


> Sorry. Wenn du auf einem Stream verschiedene Datentypen verwenden willst, dann solltest du den DataInputStream und den DataOutputStream verwenden.



Hab davon noch nie gehört ;-)
Muss ich das dann für dein Protokoll auch? 
Oder wie merge ich das byte mit dem integer und dem Daten-String zu einem byte[] Array für den Stream?

Ich habe bisher über Sockets immer nur mit Strings gearbeitet und mein "Protokoll" war halt eine Art CVS mit Trennzeichen etc.

Gruß


----------



## tuxedo (1. Okt 2009)

Wenn du davon noch nix gehört hast: Wie wär's mit API anschauen?

Und ja: Auch dann brauchst du noch mein Protokoll (oder etwas das ähnliches macht).

Bei der Dateiübertragung kommst du mit Strings etc. nicht weit.

- Alex


----------



## boehmi (1. Okt 2009)

Hallo, ja schon klar, ich habe das jetzt auch so implementiert und nutze BufferedIn/OutputStream.
Funktioniert auch soweit alles.

Allerdings zeigen sich extreme Geschwindigkeitsunterschiede zwischen verschiedenen Dateiformaten.
Woran kann das liegen?


----------



## tuxedo (2. Okt 2009)

Die Dateiformate KÖNNEN NICHT ausschlaggebend sein sofern du keinen zusätzlichen Zip*Stream verwendest.

Den nagle Algo hast du noch nicht deaktiviert? --> Use Socket's setTcpNoDelay() - Real's Java How-to
Das bringt nochmal eine verringerte Antwortzeit um locker 100ms bei kleinen Paketen  (zumindest in meinen Tests).

Um mehr sagen zu können brauchts schon etwas Beispiel-Code ...


----------



## boehmi (2. Okt 2009)

Ja ich verstehe es auch nicht, aber es ist so ;-)

Senden: 


```
try {
				int bytesRead=0;
				while ((bytesRead = buffRead.read(buffer)) > 0) {
					// 					 4= length of bid
					  length = intToByte(4+buffer.length); 
				      out.write(61);
				      out.write(length);
				      out.write(bid);
				      out.write(buffer);
				      out.flush();
				 }
				 buffRead.close();
				 send(62, Integer.toString(id));
			} catch (IOException e) {
				// TODO Auto-generated catch block
				send(63, Integer.toString(id));
			}
```

Empfangen:

```
byte[] a = new byte[1];
			  byte[] len = new byte[4];
			  byte[] msg = new byte[8187];
			  
			  int cmd = 0;
			  int length = 0;
			  while(true) {
				  msg = new byte[8187];
				  try {
					if (in.read(a,0,1) > 0) {
						cmd=a[0];
						in.read(len,0,4);
						length=byteArrayToInt(len, 0);
						in.read(msg,0, length);
						evaluateMessage(cmd, msg);
					}
				  } catch (IOException e) {
					e.printStackTrace();
				  }
			  }
```

Schreiben


```
else if(command==61) {
				  byte[] b = subArray(msg,0,4);
				  int id= byteArrayToInt(b, 0);
				  
				  int pos = Lists.getDownloadPos(id);
				  if (pos > -1) {
					  	
					    File f = new File("C:\\downloads" + File.separator + Lists.downloadList.get(pos).getFilename());
					    if (!f.exists())
							try {
								f.createNewFile();
							} catch (IOException e) {
								
							}
							
						BufferedOutputStream writer = null;
						boolean open = true;
						do {
							open = true;
							try {
								writer = new BufferedOutputStream(new FileOutputStream(f, true));
							} catch (FileNotFoundException e1) {
								// TODO Auto-generated catch block
								open=false;
								
							}
						} while (open==false);
						
					    writer.write(msg, 4, msg.length-4);
					    writer.flush();
					    writer.close();

					    
					}

				  }
```


Der Code ist teilweise noch unsauber


----------



## tuxedo (2. Okt 2009)

1) Ich seh nix von DataInput/OutputStream. Die int-to-byte Konvertierung etc. nimmt dir diese Art von Stream komplett ab. Ich rate dir dringend diesen Streamtyp anzuschauen.
2) Das ist ein absoluter Knieschuss wenn du für jedes Dateistückchen die Datei neu öffnest, das Stückchen anhängst und wieder schließt. Öffne die Datei wenns los geht und lass sie solange offen bis sie komplett da ist. Jedes Dateihäppchen wird dann "on-the-fly" direkt in den Stream der Datei geschrieben.
3) Auf meine Frage bzgl. Nagle-Algo bist du gar nicht eingegangen.

- Alex


----------



## boehmi (2. Okt 2009)

1) Okay werd ich tun 
2) ja das mit den Download-Threads kommt erst noch, dort bleibt die Datei dann natürlich durchgängig geöffnet
3) Nein habe ich nicht deaktiviert, werde mir deinen Link mal anschauen, hab heute morgen keine Zeit gehabt

Vielen Dank schonmal


----------



## tuxedo (2. Okt 2009)

boehmi hat gesagt.:


> 2) ja das mit den Download-Threads kommt erst noch, dort bleibt die Datei dann natürlich durchgängig geöffnet



Na du solltest dich nicht *jetzt *über Performance-Probleme wundern wenn du erst *später *ein jetzt schon offensichtliches Performanceproblem beheben willst.

- Alex


----------



## boehmi (2. Okt 2009)

Threads kommen ja erst zum Tragen wenn ich mehrere Transfers gleichzeitig laufen lasse.
Die "Probleme" bzw. Unregelmäßigkeiten treten ja aber schon auf, wenn ich nur 1 Datei übetrage.
Eine Textdatei fließt richtig flott durch (20Mb/s), während z.b. ein JPEG von und zu localhost nur auf vllt. 500kb/s kommt.
Einen Zipstream verwende ich (bisher) noch nicht.


----------



## boehmi (4. Okt 2009)

Hallo,
habe es jetzt auf Threads umgestellt und auch die Datei beim Empfangen permanent geöffnet. Der Speed entspricht jetzt auch der maximalen Festplatten lese/schreibrate ;-)
Vielen Dank erstmal

Allerdings tritt noch ein Problem auf (auch schon ohne Threads):

Beim Empfangen kommt der "Header", also die ersten paar Byte (Command+Länge+FileID) in ganz seltenen Fällen nicht mit an.
Wenn ich die gleiche Datei immerwieder sende tritt der Fehler auch immer an verschiedenen Stellen auf.
Und zwar etwa bei 1 von 30.000 Dateistückchen.

Der Sende-Code ist noch der selbe wie in meinem vorigen Post.

Der Header, den ich beim Senden in den OutStream schreibe:

```
out.write(61);
out.write(length);
out.write(bid);
```
scheint irgendwie nicht anzukommen 

Dadurch, dass der Header fehlt nimmt er dann die ersten 5 Byte der Nutzdaten und interpretiert sie als Header, wodurch es dann zu Exceptions kommt.
Habe diese mal abgefangen und ein paar Debugausgaben gemacht:

Empfangen:

```
if (in.read(a,0,1) > 0) {
						cmd=a[0];
						in.read(len,0,4);
						length=byteArrayToInt(len, 0);
						try {
							msg = new byte[length];
						} catch (OutOfMemoryError e) {
							msg = new byte[32];
							in.read(msg);
							System.out.write(lastmsg);
							System.out.println("\n----");
							System.out.println(lastmsg.length);
							System.out.write(a[0]);
							System.out.write(len);
							System.out.println("\n-");
							System.out.write(msg);
							System.out.println("\n" + 1);
						}
						in.read(msg,0,length);
						}
						lastmsg = msg;
						evaluateMessage(cmd, msg);
					}
```


Ausgabe beim Senden einer Textdatei mit alphabetisch geordneten Wörtern:

[...]
slawejagtee
slawejagtel
slawejag
----
8187   <-- länge der letzten nachricht
ter    <-- hier sollte eigentlich der Header sein, beginnend mit einem = für 61

-
slawejagters
slawejagtery
slaw


Länge der letzten nachricht ist im normalfall 8187, es wurde also nicht zu viel ausgelesen.
Auch am Ende der letzten Nachricht (Teil vor ----) hängt der Header nicht.

Interessant ist der Teil in dem ich den Header ausgeben lasse (a[0] und len), denn dieser zeigt genau die Buchstaben+Zeilenumbruch, die nun weiter ins File geschrieben werden müssten.


Wo ist der Header hin?
Warum tritt der Fehler so selten an unvorhersagbaren Stellen auf?

Vielen Dank


----------



## tuxedo (5. Okt 2009)

Hast du mal gecheckt ob die empfangenen Dateien auch korrekt sind? Sprich richtige Größe und byte-gleich mit der gesendeten?

Evtl. hast du noch ein Problem mit dem übermitteln der Größe und dem damit verbundenen umrechnen von "Integer" nach "byte[4]". Deshalb auch mein dringender Ratschlag: DataInputStream/DataOutputStream.

Des weiteren: flush() an der richtigen Stelle setzen. Vorzugsweise nach jedem Paket, bzw. vor dem Senden eines weiteren Pakets. Allerdings erklärt das noch nicht dein "durcheinanderkommen". 

- Alex


----------



## boehmi (5. Okt 2009)

Hallo, ja die empfangenen Dateien sind alle korrekt.

flush() sitzt doch nach dem Paket?
Oder meinst du nach jedem einzelnen write?

Ich werds jetzt mal auf DataIn/Outputstream umstellen.
Gibt es einen unterschied zwischen read() und readFully() ?


----------



## tuxedo (5. Okt 2009)

boehmi hat gesagt.:


> Gibt es einen unterschied zwischen read() und readFully() ?



Doofe Frage, doofe Antwort: Natürlich.

Wenn du mehr wissen willst: Schau in die API Doc ...

- Alex


----------

