Heiner KückerRemote Protocol | 
   |
| 
     
     Home Java-Seite Bit Packed Array ASM Improved heterogene Map, HMap Constraint Code Generator JSP WorkFlow PageFlow FlowControl Page Flow Engine Web Flow Engine Control_and_Command JSP_Spreadsheet Code-Generator für Option-Either-Stil in Java verbesserter Comparator Fluent-Interface Code-Generator auf Basis einer Grammatik Visitor mit Multidispatch for-Schleife mit yield-return Kognitions-Maschine semantisches Netz Domain Parser Codegenerator_für hierarchische Datenstrukturen Expression_Engine Formula_Parser Thread Preprocessor State Transition Engine AspectJ Java_Explorer DBF_Library Kalender_Applet SetGetGen BeanSetGet CheckPackage LineNumbers GradDms Excel-Export StringTokenizer JspDoc JspCheck JSP-Schulung Java Server Pages Struts Ascii-Tabellen- Layouter Ascii-Baum- Layouter Ascii-Art-Fluss- Diagramm- Parser AsciiArt AssignmentMatrix Layouter StringSerial Silbentrennung JDBC_Schlüssel- Generierung bidirektional/ unidirektional gelinkte Liste Java_Sitemap Generator XmlBuilder RangeMap StringFormatter VersionSafe XCopy JTextField CommandLine- ParamReader Bitmap-Grafik MultiMarkable- Buffered- InputStream JavaCache JdomUtil CollectionUtil XML Really Pull Parser Log-Filter Remote-Protokoll Sudoku-Generator Delegation statt Mehrfachvererbung Disjunct Interval Set WebCam_Demo Weiterentwicklung_Java Alaska-XBase++-Seite Projekte Philosophien Techniken Konzepte Sudoku Kontakt / Impressum Links SiteMap Letzte Aktualisierung: 10.05.2007  | 
    
Remote Protocol Serialisierung und Deserialisierung von Java-Objekten
 Serialisierung und Deserialisierung von Java-Objekten zur Übertragung über Socket mit Streams oder Java-NIO. 
Das Remote Client-Server-Protokoll
----------------------------------
Ziel ist es, ein performantes Übertragungsprotokoll zwischen dem
einem Remote Client und Server festzulegen. Dabei soll auch ein
Server, der in PHP geschrieben wurde, unterstützt werden.
Allgemeines
-----------
Die Standard-Serialisierung/-Deserialsierung in Java erzeugt sehr
grosse Datenmengen. So wird zum Beispiel ein java.util.Date in 82
Byte serialsiert, wobei nur 8 Byte (das interne long time)
Nutzdaten sind.
Durch das hier gezeigte Protokoll soll diese Datenmenge reduziert
werden, zum Beispiel soll ein java.util.Date nur zu seinen 8 Byte
Nutzlast und zusätzlich einem Byte für die Erkennung des
Datentyps serialsiert werden.
TODO
Ein Datum ohne Zeitangabe könnte sogar in 3 Byte Nutzlast
serialsiert werden, 5 Bit für den Tag im Monat (1 bis 31), 4 Bit
für den Monat (1 bis 12). Dann bleiben noch 15 Bit für das Jahr,
was den Bereich von 0 bis 32768 bzw von -16383 bis -16384
erlaubt. Dies sollte für alle kaufmännischen Probleme reichen.
Ich verstehe also das Jahr-2000-Problem nicht, es hätte nicht
auftreten müssen.
Zum Test des Protokolls habe ich einen Java-NIO-Server
(ProtocolServerNonBlocked) und einen Client (ProtocolClient)
geschrieben.
Dieser Code kam nie produktiv zum Einsatz und wurde nie intensiv
getestet.
Der Protokoll-Umschlag
----------------------
Da das TCP/IP-Protokoll keine Information liefert,
wann ein gesendeter Datenblock vollständig ist,
muss eine Längen-Information mitgesendet werden.
Die ersten vier Byte der Nachricht stellen die Längen-Information
dar. Das höchstwertigste Byte wird als erstes gesendet.
Dadurch ist die Grösse eines Datenblockes auf 4 GigaByte
begrenzt, was ausreichen sollte.
Nach den vier Byte für die Länge werden zwei
Byte für die gewünschte Operation gesendet.
Diese beiden Bytes gehören schon zur Gesamt-Information
und werden bei der Länge mitgezählt.
Das erlaubt es 65536 Operationen zu definieren.
In den Client- und Server-Programmen werden diese
Nummern auf benannte Operationen gemappt.
Nach dem Operations-Code folgt ein Byte mit der
Anzahl der Parameter.
Dadurch ist die Anzahl der Parameter auf 255 beschränkt.
Durch die Angabe der Parameter-Anzahl ist es möglich,
die gleiche Operation mit unterschiedlichen Parametern
aufzurufen.
Werden mehr Parameter benötigt, müssen diese in
Arrays verpackt werden.
Achtung: das höchstwertigste Bit der Parameter-Anzahl wird als
Signal benutzt, ob die gesamten Parameter komprimiert sind (GZIP).
Negative Parameter-Anzahlen gelten als komprimiert.
Dadurch reduziert sich die Anzahl möglicher Parameter auf 127.
Diese Lösung ist notwendig, weil sich mit einem GZIP-Umschlag nur
ein einzelner Parameter komprimieren lässt.
Danach folgen die Parameter der Operation.
Beim Response wird der Operations-Code zurückgesendet.
Dies ist zwar redundant, wird aber bei evtl. später
zu implementierenden asynchronen Antworten benötigt.
Die Response enthält ebenfalls ein Byte für die Anzahl der
Rückgabe-Werte mit dem höchstwertigen Bit als Markierung für
die Komprimierung der Rückgabe-Werte.
Request:
                      4 - 5              6 - 9               10 - 11                12                       13 -
                      0 - 1              2 - 5                6 -  7                 8                        9
+------------------+-------------------+-------------------+----------------------+------------------------+--------------------+
| Byte 0 - 3 Länge | 2 Byte Request-ID | 4 Byte Session-ID | Byte 10 - 11 OP-Code | Byte 6 Parameter-Anzahl |   Parameter-Daten  |
+------------------+-------------------+-------------------+----------------------+------------------------+--------------------+
                   |<--------------------------------------------- Länge des Request ------------------------------------------>|
Response:
                      4 - 5              6 - 9               10 - 11                12                            13 -
                      0 - 1              2 - 5                6 -  7                 8                             9
+------------------+-------------------+-------------------+----------------------+------------------------------+--------------------+
| Byte 0 - 3 Länge | 2 Byte Request-ID | 4 Byte Session-ID | Byte 10 - 11 OP-Code | Byte 6 Rückgabe-Werte-Anzahl |   Rückgabe-Daten   |
+------------------+-------------------+-------------------+----------------------+------------------------------+--------------------+
                   |<---------------------------------------------- Länge der Response ---------------------------------------------->|
2 Byte für eine Request-ID TODO
Nachfrage: Session-Id 4 Byte im Header mitsenden ? (Kann für anonyme Request's ausgenullt werden)
Der gesamte Request und Response wird verschlüsselt.
Bei jedem Request und Response wird der positionsabhängige
Verschlüsseler neu gestartet, um ein aus dem Takt
(Synchronisation) laufen der Ver- und Entschlüsseler zu
vermeiden.
Offene Frage: soll die Längen-Information sowie Request-ID und Session-ID mit verschlüsselt werden ?
Bei Bekanntsein dieser Tatsache könnte dadurch das Knacken
der Verschlüsselung erleichtert werden.
Verwendung der Serialisierungs-Formate für die Persistierung
------------------------------------------------------------
Die Serialisierungs-Formate sind unverändert zum Abspeichern
von Konfigurationsdaten oder temporär zu persistierender
Daten (persistenter Client-Cache) geeignet.
Dabei ist aber immer eine serielle Arbeitsweise
erforderlich. Für die Arbeit mit wahlfreiem Zugriff zur RAM-
Einsparung müssen alternative Formate mit festen Längen
verwendet werden (oder besser eine Embedded-DB wie Derby oder
db4o).
Zum Absichern der Versions-Migration (zusätzliche Felder bei
neuen Programm-Versionen) ist ein Versions-Header erforderlich.
Dazu können weitere fachliche Informationen (Kopf-
Informationen like-DBASE oder Inhalts-Informationen
(Datentyp semantisch) ) kommen.
Das Abspeichern der Body-Length ist beim Persistieren im
Datei-System nicht nötig.
+-------------------------------------------------+-------------+
| Versions-Nummer (2 Byte, höchstwertiges zuerst) | Daten-Bytes |
+-------------------------------------------------+-------------+
Datentypen
==========
Jeder Parameter- und Rückgabewert wird durch das erste Byte
in seinem Type identifiziert.
Bei Notwendigkeit wird dieser Type durch weitere Bytes
genauer spezifiert.
Typen mit implizitem Wert
-------------------------
Diese Typen führen keinen Wert mit, da
der Wert im Typ schon enthalten ist.
n - Null  (char 110)
t - true  (char 116)
f - false (char 102)
Elementare Datentypen
---------------------
-Byte vorzeichenbehaftete 8-Bit-Ganzzahl
+-------------+------------+
| b (char 98) | Daten-Byte |
+-------------+------------+
-Short vorzeichenbehaftete 16-Bit-Ganzzahl
+--------------+---------------------+-----------------------+
| s (char 115) | Höchstwertiges Byte | Niederwertigstes Byte |
+--------------+---------------------+-----------------------+
-Integer:
32-bit Integer
+--------------+---------------------+--------+--------+-----------------------+
| i (char 105) | Höchstwertiges Byte | Byte 1 | Byte 2 | Niederwertigstes Byte |
+--------------+---------------------+--------+--------+-----------------------+
-Long:
64-bit Long
+--------------+---------------------+-----------------+-----------------------+
| l (char 108) | Höchstwertiges Byte | Byte 1 - Byte 6 | Niederwertigstes Byte |
+--------------+---------------------+-----------------+-----------------------+
-komprimiertes Short, komprimiertes Integer und komprimiertes Long
Es ist möglich, dass der Wert eines int in den Wertebereich
von byte oder short passt.
Es ist möglich, dass der Wert eines long in den Wertebereich
von byte, int oder short passt.
Für diesen Fall werden nur die jeweils notwendigen Bytes
serialisiert.
Beim Deserialisieren muss aber wieder der originale Typ
bereitgestellt werden, um Probleme mit unterschiedlichen
Klassen (ClassCastException, ArrayStoreException,
Fehlschlagen von instanceof) zu vermeiden.
  Typ  | Speicher-Typ | Prefix-Code
-------+--------------+-------------
 short | byte   (8)   |     1
 int   | byte   (8)   |     2
 int   | short (16)   |     3
 long  | byte   (8)   |     4
 long  | short (16)   |     5
 long  | int   (32)   |     6
-Float 4-Byte (32 Bit) Gleitkommazahl:
Code 'F', weil 'f' schon für false verwendet wird
+--------------+---------------------------------------------------+
| F (char 70)  | 4 Byte im IEEE 754 floating-point "single format" |
+--------------+---------------------------------------------------+
-Double 8-Byte (64 Bit) Gleitkommazahl:
+--------------+------------------------------------------+
| d (char 100) | 8 Byte im IEEE 754 floating-point format |
+--------------+------------------------------------------+
Konvertierung in Java
  Double.longBitsToDouble(long bits)
  Double.doubleToLongBits(double value)
Konvertierung in PHP
 pack( "d", 
Download der Quelldateien REMOTE_PROTOCOL.zip Installation: Anlegen eines Projektes in der IDE Ihrer Wahl. Start mit ProtocolServerBlocked#main bzw. ProtocolServerNonBlocked#main bzw. TestServer#main (Server unbedingt immer zuerst starten) und dananch ProtocolClient#main bzw. TestClient#main. 
Achtung: Erweiterungen und Fixes stelle ich ohne Historie
und ohne Ankündigung hier bereit. Lizenzbedingungen: 
Die Programme, Quelltexte und Dokumentationen können ohne
irgendwelche Bedingungen kostenlos verwendet werden.  |