Fragen zum Ablauf

Anton2k

Aktives Mitglied
Hallo
Beim ausprobieren der Netzwerkmöglichkeiten sind mir ein paar fragen aufgekommen. Ich wollte zur Übertragung Serialisierte Datentransferobjekt nutzen.
Ok in dem Objekt wird z.B. in einem Byte Array eine Datei übertragen an den Server. Da kommt mir schon die Frage 1.
Solange der Client an den Server schickt ist sein OutputStream ja von diesem Objekt vollgestopft und das Programm blockiert bis die übertragung fertig ist. Sollte man in diesem fällen das ganze auch in einen extra Sende Thread auslagern und evtl. mit einer Sendewareschlange Arbeiten?

Frage 2.
Wie Arbeitet java auf diesem Out-/Inputstream.
Schickt der Server laufend Daten oder wartet er bis die Daten angekommen und vom Protokoll besätigt wurden?
Etwas aufgedröselter:
Der Server schickt DatentransferObjekt1
Der Server hat seine OutputStream leer
Pack direkt DatentransferObjekt2 rein oder wartet bis er ein OK emfangen vom Client bekommt bis er es reinpackt? Kümmert sich Java selbst darum wenn Teile eine übertraung verlohren gehen?

Frage3.
Gibt es eine Informationsseite über Java und Netzwerk die sich weniger mit dem Code an sich auseinander setzt sondern ehr mit den Funktionen, Abläufen und dem drumherum.
 
T

tuxedo

Gast
Zu 1)
Was du brauchst ist ein Protokoll

Zu 2)
Die genannten Streams (Object*Stream) sind sehr rudimentär. Ein ACK wird da nicht gesendet. Aber um die Performance zu steigern wird ein zweimal abgeschicktes Objekt nur 1x wirklich geschickt. Beim zweiten mal wird eine ID/Referenz geschickt. Da beide Seiten das Objekt beim ersten mal schon gecached haben, beschleunigt das senden der ID beim zweiten mal die Übertragung je nach Objektgröße immens.

Um Dateien zu übertragen nimmt man für gewöhnlich keine Object*Streams. Man nimmt entweder vorhandene Protokolle (ftp?), oder man bastelt sich ein eigenes Protokoll.

Zu 3)
Öhm, mir ist da nix bekannt. Java ist da recht rudimentär. Streams sind einfach Kanäle in die man Daten am einen Ende reinwirft und am anderen Ende kommen sie raus. Protokolllogik findet da eigentlich fast keine statt (bis auf das darunterliegende TCP/UDP, was aber das Betriebssystem erledigt).

ALlgemein kann man sagen: Erst schauen was kommuniziert werden soll, und dann das passende Protokoll suchen. Sofern es noch keines für den spezialfall gibt: Selbst basteln. Hier entweder auf nackigen Streams aufsetzen (evtl. noch Data*Stream), oder, sofern mehr Objekte als Daten gesendet werden, auf die Object*Streams zurückgreifen.

- Alex
 

Anton2k

Aktives Mitglied
Ok bleibt nur ein Eigenes Protokoll wie ich das so sehe. Im Prinzip hab ich sowas schon mal gemacht in Delphi damals aber da gab es einige kontroll mechanismen von einem Fertigen Packet die ich benutzt habe.

Also ich erstelle eine Protokoll was alles transportieren kann.
Ersten 2 Byte werte werden die länge des Datensatzes. Die nächsten 2 werden der Typ des Packetes. Rest wird zum Transport verwendet.
[länge1][länge2][Typ1][Typ2][Daten]
Da ich zwei Byte als wertebereich verwenden möchte hätte ich 1024Byte als Datensatzgröße gedacht was bei einer DSL Verbindung gut klappen sollte oder ist das noch zu wenig für aktuelle Internetleitungen. Selbst ISDN schafft ~8KB/s im Upload.

Ok das zu meiner Grundidee.
Ich habe also beim Client eine Funktion die mir meine Packete zusammen bastelt und rausschickt.
Der server liest diese Daten und hängt sie laufend an ein Byte Array an.
Er liest die ersten 2 Byte und schaut ob das gesammt Array lang genug für den Datensatz ist. Wenn ja liest er ihn vorne raus, und der rest rückt nach. (oder kann man das gleich auf dem InputStream erledigen ohne sie erst von dort auslesen zu müssen?)
So habe ich Datenpacket für Datenpacket zur verfügung.
ABER wie schlau ist diese unterste ebene von Java (oder TCP/IP falls sich das schon drum kümmert)
Ich schicke zwei Datensätze nacheinander los. Das Betriebsystem entscheidet, ich mag aber nur 512byte große Packete verschicken und Teilt es auf. Das heist ich habe 4 Packete die evtl. zu unterschiedlichen Zeiten ankommen. Wie landen diese im InputStream?

Weiteres Problem was ich sehe ich habe 1024-4 Byte (2für länge, 2 für Typ)Pro Datensatz zur verfügung. Also bekomme ich 1020 Textzeichen gesendet (Unicode lasse ich mal aussen vor für das beispiel). Wenn ich mehr Text schicke muss ich mich auch noch um ein Zerlegen und zusammenfügen kümmern was unnötig viel Arbeit ist. Würde für mich bedeuten ich ändere die längen Byte auf 4 stück. Das reicht locker für alles aus was in meinem Programm anfällt. Übergebe ich dieses weit aus größere Array (Muss ja nicht die ganze größe ausnutzen) an den OutputStream bekomme ich dann Probleme?
 
T

tuxedo

Gast
Viel einfacher: Denk einfach nicht an irgendwelche Paketgrenzen/Größen. Das managed der TCP-Stack für dich.

Ich hab ein Protokoll das so aufgebaut ist:

Header:
1 byte: Definiert die Art der Nachricht
4 byte: laufende Nummer die vom Sender mit jeder Nachricht inkrementiert wird (dient dem zuordnen von Antworten auf die Anfrage/Nachricht)
4 byte: Länge des Bodys
Body:
x bytes. Hängt von der Art der Nachricht ab.

Damit hab ich pro Nachricht die gesendet wird mind. 9 bytes "overhead". Das sollte sogar noch für eine Modemverbindung reichen. HTTP und XML und Co. haben da schon deutlich mehr Overhead. Und auch das hat man zu Modem-Zeiten schon genutzt.

Wenn du ein schickes Framework suchst mit dem du ein sauberes Protokoll erstellen kannst schau dir mal folgende Links an:

Apache MINA - Index
Netty - the Java NIO Client Server Socket Framework - JBoss Community

- Alex
 

Anton2k

Aktives Mitglied
Ok Danke, dann werde ich das wohl so lösen und mir selbst etwas erstellen. Da es ein Hobby Projekt ist wird es nicht so stark auf die Performance ankommen. Wenn ich es einmal wirklich selbst mache lerne ich mehr und verstehe das ganze auch besser.

Bleibt die Frage wie Manipuliert man bei Java am besten das Byte Array?
Meine suche hat bis jetzt nicht wirklich funktionales ergeben um meinen Header zu bauen bzw. dann mit Daten zu Füttern. So etwas in der Art wie wandel diesen Int in Byte um , kürze ihn auf 2 Byte und schreibe ab Frame[2] diese 2 Byte. Oder hilft da nur von Hand ne Funktion machen die das aufdröselt und Byte für Byte einfügt?
 
T

tuxedo

Gast
byte[]s manipulieren war gestern.

Nimm einen DataOutputStream und DataInputStream.
Da kannst du bequem alle Möglichen Typen reinstecken und auf der anderen Seite so wieder auslesen. Das konvertieren (und auch wieder zurück) von z.b. Integer -> byte[4] übernimmt die Streamklasse für dich.

- Alex
 

Anton2k

Aktives Mitglied
Auch ne den Tag drüber nachgrübeln fällt bei mir nicht so recht der Groschen wie ich damit umgehen soll. Vermutlich denke ich zu kompliziert.

Baue ich mein Frame vorher zusammen bevor ich es an den Socket.OutputStream übergebe? Oder schreibe ich direkt mit meinem DateOutputStream in den Socket.OutputStream?

Irgendwie kann ich mit den Streams noch nicht genug anfangen. Da werde ich wohl noch etwas recherchieren müssen.

Edit: Hab gerade den ByteArrayOutputStream entdeckt. Wenn ich das mit dem DataOutputStream verkette solle ich mein Frame doch in einem byte[] zusammen setzen können. Werde ich mal probieren.
 
Zuletzt bearbeitet:
T

tuxedo

Gast
Lass das byte[] und den ByteArrayOutputStream weg. Schreib direkt in den Stream und gut ist. Evtl hinterher noch ein flush() aufrufen....

- Alex
 

Anton2k

Aktives Mitglied
Irgendwo blockiert mein Kopf da gerade. Angenommen ich möchte solche Frames haben
[Typ][länge]][Daten]

Habe meinen DataOutputstream "dos" erstellt.
Code:
			dos.writeInt(laenge);
			dos.writeShort(13);
			dos.writeUTF("Hallo");
			dos.writeInt(2500);
Sprich ich müsste für jedes Packet ne komplette extra Funktion schreiben. Welche alle Werte annimmt in diesem falle. (int typ, String text, int irgendwas) Dann müsste ich die länge berechnen.
in diesem Beispiel:
Header (2 Byte + 4 Byte)+Daten(4byte +String+4Byte) *kleine Randfrage weiter unten.

Das ganze wandert durchs Netzwerk. Auf der anderen Seite lese ich das ganze raus in mein Buffer Array (Nehme ich mal an weil es ja nach dem ersten lesen aus meinem InputStream verschwindet).
Lese die länge raus, und prüfe ob genug im Buffer ist das mein Frame komplett angekommen ist.
Wenn dem dann so ist lese ich die werte raus durch den DataInputStream raus. Die länge benötige ich also theoretisch nur 1x um zu merken ob alles angekommen ist. Der Typ entscheidet dann was ich in informationen aus diesem frame ziehen kann.

* zur randfrage. Habe das ganze testweise in das Byte Array schreiben lassen und mir das Feld für Feld ausgeben lassen. Der UTF String wird dort in etwa so abgelegt:
0,länge,text
die länge entsprich immer den Byte die auf das Längenfeld folgen. Bei einem nur einem Zeichen aus dem ASCII Bereich 1, bei nur einem UTF zeichen 2. Soweit logisch. Nur wie komme ich die binäre länge eines Strings heraus.
 
T

tuxedo

Gast
Moin,

also prinzipiell bist du schon auf dem richtigen Weg. MINA würde so ein Protokoll perfekt kapseln, so nebenbei erwähnt :)

Aber zurück zum Thema:

Sprich ich müsste für jedes Packet ne komplette extra Funktion schreiben.

So ist es. Und das ist mehr oder weniger auch gang und gebe.
In meiner SIMON Implementierung ist das ähnlich. Ich habe auf interner API Ebene für alles wichtige eine extra Methode die die zu sendenden Daten entgegen nimmt, den Header formuliert und in die Nachricht in die Sende-Queue einreiht.

Zur Sache mit dem String:

Nun. Da wird es etwas komplizierter. Klar, man kann den Header nicht vervollständigen und eine Größe des Bodys angeben wenn man den Body noch gar nicht ausgewertet hat. Für den Fall dass der Body eine beliebige Länge haben kann, ist es denke ich legitim den ByteArrayOutputStream zu nehmen und diesen mit den Body-Daten zu füllen. Dann holt man sich aus dem BAOS das byte[] und kann schauen wie groß der Body ist. Damit vervollständigt man die Header-Information und kann dann in einem Rutsch alle Header-Daten in den DOS schreiben, gefolgt vom byte[] des BAOS das den Body darstellt.

In SIMON hab ich das ähnlich geregelt. Nur dass ich da nicht mit byte[] sondern mit ByteBuffer (was besser zum Java NIO Konzept passt) arbeite.

- Alex
 

Anton2k

Aktives Mitglied
Ok schon mal vielen Dank soweit für die Geduld.
Das Versenden klappt jetzt und ich kann mir das ganze auch schön vom Server ausgeben lassen was ankommt. Stimmt also überein.

Jetzt hab ich aber wieder nen hänger mit dem Verarbeiten. Eine Lesezugriff auf den OutputStream liest mir die Sachen ja weg. Wie Arbeitet man jetzt sinnvoll mit dem Input Stream?

Hatte ja erst einmal die idee ihn einfach in ein byte Array reinzulesen und dann damit weiter zu machen. Aber ein Array ist ja statisch. Sprich ich kann zwar wenn ich es einmal groß genug angelegt hab den Eingangsstrom in das Array umlenken. Und diesen wiederrum auslesen. Mein Frame rausziehen, und müsste den Rest dann umkopieren in ein neues Array damit es nach vorne rutscht.
Erscheint mir viel zu Kompliziert und unpraktisch. Da muss es ein sinnvolleres vorgehen geben wie ich meine Frames wieder auseinander genommen bekomme.
 
T

tuxedo

Gast
Na du stellst dich an ...

Du weißt doch wie eine komplette Nachricht aussieht?


Code:
Header {
Typ:byte
Länge:int
}
Body {
Daten:byte[]
}

Du machst also einen Thread der auf dem DataInputStream in einer Endlosschleife liest.

Pseudocode:

Code:
while(true){
   byte type = dataInputStream.read();

   switch(type) {
      case 0x00 :
          handle00Message();
      case 0x01 :
          handle01Message();
// ...
      case default :
         logError("Message type unknown");
   }
}


// .....


private void handleXYMethod() {
   int length = dis.readInt();
   byte[] data = new byte[length];
   dis.readFully(data);
   // do something with body ...
   return
}

So oder so ähnlich könnte das aussehen.

- Alex
 

Anton2k

Aktives Mitglied
Also gehe ich davon aus das alle Daten von Header zumindest da sind?

Dann ist der schlüssel wo meine überlegung nicht weiter gekommen ist diese Zeile:
dis.readFully(data);
Wenn ich das in der Dokumentation richtig sehe blockiert sie solange bis das Array gefüllt hat.
Heist vermutlich wenn noch nicht genug da ist blockiert sie so lange bis genug zum lesen da war?
Besteht nicht generell die Gefahr das mein Header noch nicht komplett angekommen ist und dadurch mehr oder weniger alles durcheinander kommt?

Wenn meine Bedenken unbegründet sind dann ist mir jetzt aber klar wie es abzulaufen hat.
 
T

tuxedo

Gast
Also gehe ich davon aus das alle Daten von Header zumindest da sind?

Dann ist der schlüssel wo meine überlegung nicht weiter gekommen ist diese Zeile:
dis.readFully(data);
Wenn ich das in der Dokumentation richtig sehe blockiert sie solange bis das Array gefüllt hat.
Heist vermutlich wenn noch nicht genug da ist blockiert sie so lange bis genug zum lesen da war?
Besteht nicht generell die Gefahr das mein Header noch nicht komplett angekommen ist und dadurch mehr oder weniger alles durcheinander kommt?

Wenn meine Bedenken unbegründet sind dann ist mir jetzt aber klar wie es abzulaufen hat.

:)

Du hast es hier mit Blocking IO zu tun. D.h. jeder read() Befehl blockiert bis die Daten vollends da sind.

Machst du ein readInt() (was 4 bytes aus dem Stream benötigt), dann blockiert das so lange bis 4 bytes gelesen werden können (sprich, diese auch da sind).
read(byte[]) ist hier eine Ausnahme. Diese read Methoden liefern zurück wieviel sie lesen konnten. Der DataInputStream bietet aber readFully() Varianten an. Diese blockieren solange bis das komplette Array gefüllt wurde.

Du musst dir also keine Sorgen machen dass da etwas durcheinander kommt. Einfach loslegen und vorher evtl. nochmal die JavaDoc der jeweiligen Methode anschauen. Das reicht aus.

- Alex
 
Zuletzt bearbeitet von einem Moderator:

Anton2k

Aktives Mitglied
Ok die Information das es "Blocking IO" ist hat bei mir ziemlich alle Schranken die ich noch im Kopf hatte gelöst. Das war das entscheidende was mir gefehlt hatte und mir wurden Streams gleich viel Sympatischer.

Vielen dank für die Hilfe soweit. Wenn neue Probleme da sind melde ich mich wieder.
 

Ähnliche Java Themen


Oben