VS: Verteilte Systeme Kap. 02 Kommunikation Prinzipien und Realisierung mit Sockets Wolfgang Wiedermann FH Regensburg FH Teil 1 1 Inhalt: Kommunikation Übersicht: Schichten Sockets Verbindungsfreie Sockets: UDP-Sockets Verbindungsorientierte Sockets: TCP-Sockets Marshalling, Unmarshalling Request/Reply-Protokolle RPC Remote Procedure Call Nachrichten-Orientierte Kommunikation MOM Stream-Orientierte Kommunikation Literatur Couloris/Dollimore/Kindberg (cdk) : VS Tanenbaum (tb): VS 2 Schichten der Middleware Anwendungen, Dienste RMI, RPC, CORBA, … Web-Services Request-Reply … Protokolle UDP und TCP Middleware 3 Sockets: Allgemein Das Socket-Interface wurde zur Programmierung verteilter Anwendungen auf TCP/IP-Netzwerken für UNIX-Rechner entwickelt. Die Socket-Abstraktion setzt zwei Prozesse voraus, die auch auf demselben Rechner laufen können. Ein Socket ist dabei als ein Endpunkt einer Kommunikation im Netzwerk zu verstehen. Um eine Verbindung zu ermöglichen, braucht man zwei solcher Enden (Æ port) Dabei gibt es zwei Typen von Verbindungen verbindungsfrei: Datagramme, einzelne Pakete von Nachrichten (UDP) verbindungsorientiert: sequenzielle Bearbeitung in Streams möglich (TCP) 4 Sockets und Ports socket any port agreed port socket message client server other ports Internet address = 138.37.94.248 Internet address = 138.37.88.249 Grafik: cdk 5 Kommunikation mit verbindungsfreien Sockets Mit verbindungsfreien Sockets können einzelne Datenpakete versandt werden. Transfer Der empfangende Prozess muss ein Byte-Array zur Verfügung stellen, in das die Nachricht passt. Ist die Nachricht zu lang, so wird sie gekürzt. Senden (send) blockiert nicht, empfangen (receive) blockiert, außer wenn man einen Time-Out angibt. Die Empfangsmethode receive nimmt jede Nachricht an, die an den Socket adressiert ist. Der Absender kann aus der Nachricht ausgelesen werden. Fehlermöglichkeiten Jedes Paket ist unabhängig von den anderen. Pakete können verloren gehen. Einzelne Pakete können andere Pakete überholen. 6 Trivialansatz Remote-Kommunikation Client Server (Daten in String schreiben) On Interrupt: String parsen Request message Daten verarbeiten (warten) (Reply Message auswerten) Reply message Daten in String schr. Request Parameter1 Parameter2 Parameter3 … 7 Trivialansatz Remote-Kommunikation Request Parameter1 Parameter2 Parameter3 … Problem: So ist nur eine Funktion pro Port möglich! 8 Trivialansatz Remote-Kommunikation Request Parameter1 Parameter2 Parameter3 … Problem: So ist nur eine Funktion pro Port möglich! Lösung: OperationsID zum Request hinzufügen 9 Trivialansatz – leicht verbessert Client Server (Operation +Daten in String schreiben) On Interrupt: String parsen Request message Operation ausw. (warten) (Reply Message auswerten) Daten verarbeiten Reply message Daten in String schr. Request OperationID Parameter1 Parameter2 … RetParam2 RetParam3 … Reply RetParam1 10 Trivialansatz – leicht verbessert Request OperationID Parameter1 Parameter2 … RetParam2 RetParam3 … Reply RetParam1 Problem: Keine Zuordnung zwischen Request und Reply! (Ist nur bei nicht verbindungsorientierten, also UDP-basierten Protokollen relevant) 11 Trivialansatz – leicht verbessert – Version 2 Client Server (Operation +Daten in String schreiben) On Interrupt: String parsen Request message Operation ausw. (warten) (Reply Message auswerten) Daten verarbeiten Reply message Daten in String schr. Request OperationID RequestID Parameter1 … RetParam1 RetParam2 … Reply RequestID 12 Objektorientierung vs. Remote Function Call Prozedurales Remoting • Aufruf von entfernten Funktionen oder Prozeduren • Alle relevanten Daten werden als Parameter übergeben • per Definition Stateless Objektorientiertes Remoting • Aufruf von entfernten Methoden • Diese gehören per Definition zu Objekten • deshalb Stateful • Problem: Identifikation von entfernten Objekten 13 Request-Reply-Protokolle und Objektreferenzen Client Server Request doOperation getRequest message Auswahl des Objekts (warten) Reply message (continuation) Ausführung der Methode sendReply Objektreferenz 32 bits 32 bits Internet Adresse Port 32 bits Zeit 32 bits Objektkennung Schnittstelle Grafik: cdk 14 Operationen in einem Request-Reply-Protokoll Senden einer Anforderung an ein entferntes Objekt. Parameter: Objektreferenz, Methodenreferenz, Argumente public byte[] doOperation ( RemoteObjectRef o, int methodId, byte[] arguments) Auslesen einer Anforderung vom Server-Port public byte[] getRequest (); Senden einer Antwort an den Client. public void sendReply ( byte[] reply, InetAddress clientHost, int clientPort); 15 Aufbau einer Request-Reply-Nachricht messageType int (0=Request,1= Reply) requestId int objectReference RemoteObjectRef methodId int or Method arguments array of bytes Grafik: cdk 16 Marshalling, Unmarshalling Bei verteilten Systemen müssen Daten von einem Rechner zu anderen Rechner übertragen werden. Dazu müssen sie in eine für die Übertragung geeignete Form umgewandelt bzw. aus dieser Form wieder in eine für den Zielrechner brauchbare Form zurückgewandelt werden. Marshalling: Wandelt eine Kollektion von Datenelementen in eine für die Übertragung geeignete Form um (serialisieren). Unmarshalling: Zerlegt empfangene Daten und erzeugt daraus beim Empfänger eine identische Kollektion von Datenelementen (deserialisieren). 17 Marshalling Private Datenformate Für kleine Anwendungen kann man private Datenformate für die Abspeicherung erfinden. Z.B. kann man alle Daten als Text abspeichern, wobei die Felder durch Sonderzeichen getrennt werden. Dieses Verfahren stößt schnell an seine Grenzen. Serialisierung In diversen Frameworks gibt es für manche Klassen die Möglichkeit, Objekte zu serialisieren. Die Objekte werden auf Streams geschrieben. Diese Streams passen nahtlos zur verbindungsorientierten Kommunikation mit Sockets und über ByteStreams auch zu Datagrammen. 18 Marshalling : Datenformate XDR (Internet RPC External Data Representation) NDR (Network Data Representation vom MS -RPC) CDR (Common Data Representation CORBA) Java object serialization ASN.1 (ISO OSI) : Grafik: cdk 19 Marshalling über XML Manche Frameworks bieten die Möglichkeit, die Daten in jeweils eigenen XML-Formaten zu serialisieren. Java und .NET bieten hier eingebaute Möglichkeiten Objekt Æ XML XSD Æ Klasse, Objekte dieser Klasse dann in XML serialisieren. Bleibt man in einem Framework, gibt es kein Problem beim Marshalling/Unmarshalling Der Austausch mit anderen Systemen ist häufig zum Scheitern verurteilt. XML-Format via XSD normiert, d.h. gute Aussichten, Daten über Framework-Grenzen hinweg auszutauschen. ABER: Die Klassen in Java/C# werden generiert und dürfen danach nicht mehr verändert werden. Siehe: vs-xml-persistenz.pdf 20 Probleme mit der Kommunikation über Sockets Marshalling/Unmarshalling aufwendig Asynchrone Kommunikation fehleranfällig Die asynchrone Kommunikation passt nicht in das „normale Programmiererleben“ Abhilfe: RPC Remote Procedure Call Die Middleware übernimmt Marshalling/Unmarshalling, Transport, Zustellung, Aktivierung des Servers und Antworten. Beispiel: ein Aufruf k = add (i, j) als RPC 21 Client Aufruf: k = add (i, j); #add Server 2 1 Kennzeichen für Methode, Werte der Parameter (27 bzw. 2) auf Verbindung schreiben 00.00.00.1B Server horcht an Socket 3 00.00.00.02 4 Auspacken der Parameter Ergebnis als Rückgabewert setzen 9 00.00.00.1D X0 im RAM X1 im RAM X2 im RAM ??.??.??.?? 00.00.00.1B 00.00.00.02 X0 = add (X1, X2) 8 5 00.00.00.1D 6 7 Programmcode der add (int, int)Methode RPC: Schritt 1 und 2 Zu 1: Der Client kann die Methode add nicht direkt aufrufen, da sie in einem anderen Prozess implementiert ist. Stattdessen wird eine add-Methode eines Stellvertreters aufgerufen. Zu 2: Diese add-Methode öffnet einen Kommunikationskanal zu einem Server (z.B. über einen Socket, schreibt einen Auftragscode für das gewünschte Objekt, die Verarbeitung sowie die Parameter auf den Socket. Die Darstellung der Daten muss unabhängig von der Plattform sein. Z.B. kann der Empfänger ein Big-Endian-Computer (Z.B. Sparc: höchstwertiges Byte zuerst) sein, der Sender ein Little-EndianComputer (Z.B. Intel, niederwertiges Byte zuerst). Danach blockiert diese Methode im Stellvertreter. 23 RPC: Schritt 3 und 4 Zu 3 Der Kommunikationskanal übernimmt den Transport zum Server. Der Server muss zu diesem Zeitpunkt gestartet sein und z. B. an einem Socket „horchen: listen-Operation. Eine eingehende Verbindung wird akzeptiert. Der Server kann die Aufträge sequentiell bearbeiten oder auch parallel. In letzterem Fall bietet sich die Zuordnung der Arbeiten zu einzelnen Threads an, die je nach Bedarf vorher gestartet wurden (sog. ThreadPool), bzw. in Abhängigkeit von einem Auftrag gestartet werden. Zu 4: In jedem Fall müssen die Parameter ausgepackt werden. Sie werden aus der netzwerkunabhängigen Darstellung in die binäre Darstellung der Server-Plattform konvertiert und dort im RAM abgelegt. Auch der Platz für ein eventuelles Ergebnis wird bereitgestellt. 24 RPC: Schritte 5 … 9 Zu 5 Zu 6: Das Ergebnis wird an den Auftraggeber zurückgeliefert. Zu 8: Das Ergebnis wird in den Kommunikationskanal geschrieben. Zu 7: Die gewünschte Routine wird zum Ablauf gebracht. Das Ergebnis liegt nun im RAM (Server) vor. Die Daten werden aus dem Socket ausgelesen und im RAM-Bereich des lokalen Stellvertreters abgelegt. Zu 9: Der Wert wird als Ergebnis des Aufrufs zurückgeliefert. 25 Implementierung von RPC-Servern Sequentieller Server Problem Der Server ist in der Lage, mehrere Aufträge parallel auszuführen, z.B. über Threads. Problem Lange Wartezeiten für die Auftraggeber, auch dann, wenn der Server nicht ausgelastet ist. Paralleler Server Wenn ein Server bei der Ausführung eines Auftrags in einen Wartezustand gerät, kann er nicht auf andere Aufträge reagieren. Wer übernimmt die Synchronisierung auf der Server-Seite? Microsoft-Bezeichnungen Single-Threaded-Apartment STA und Multi-Threaded-Apartment MTA 26 Problem beim RPC Die Schnittstelle zwischen Client und Server ist so komplex, dass sie maschinell unterstützt werden muss. So muss der Code für das Ver- und Entpacken der Parameter generiert werden. Im Grunde muss ein Vertrag zwischen Client und Server aufgesetzt werden. Aus dem Vertrag heraus müssen die entsprechenden Teile für Client- und Server generiert werden: [ uuid(7a98c250-6808-11cf-b73b-00aa00b677a7), version(1.0) ] interface hello{ void HelloProc([in, string] unsigned char * pszString); void Shutdown(void); } 27 Entwicklung für RPC Der Vertrag wird in IDL aufgesetzt. Client- und Server-Anteile werden generiert. Die Anwender können ihre Software entwickeln. Die Software läuft dann mit dem Laufzeitsystem der Middleware. Also: Die Middleware greift auch in den Entwicklungsprozess für die Software ein. Clientseite Serverseite Entwickler:IDL Schnittstelle Client: Stellvertreter, Stub Client: Anwendung Server: Skeleton Server: Funktionalität 28 Entfernter Objektaufruf Grafik: ts 29 Sockets und RPC: Zusammenfassung Client-/Server-Modelle Der Server muss laufen, wenn der Client eine Anfrage absetzt. Æ Was ist mit Geräten, die nicht permanent eingeschaltet oder verbunden sind? Dies gilt für Clients (Auftrag absetzen und beenden) ebenso wie für Server (läuft nicht ständig) Abhilfe: eine Message-Oriented-Middleware MOM mit der Fähigkeit zur Pufferung der Aufträge, sei es auf der Seite des Clients oder der des Servers. Sockets können auch für eine asynchrone Kommunikation benutzt werden, der klassische RPC nicht. CORBA bietet für Objekte eine asynchrone Kommunikation mit seinen oneway-Methoden an (fire and forget). 30 Nachrichtenorientierte Kommunikation Grafik: ts 31 Nachrichtenorientierte Kommunikation Beispiele Email SMS JMS Java Messaging Service in JEE Prinzip Die Middleware nimmt die Aufträge entgegen und sorgt für die Zustellung. Wenn sich ein Client beenden will: Wenn ein Server nicht läuft: Die Aufträge liegen auf einem Kommunikationsserver und können zugestellt werden. Hier ist nur eine asynchrone Arbeitsweise sinnvoll. Auch hier liegen die Aufträge auf einem Kommunikationsserver und können zugestellt werden, sobald sich der Server als aktiv meldet. Die Steuerung kann die Kontrolle über die Server übernehmen und z.B. Transaktions-Dienste implementieren. 32 Streamorientierte Kommunikation: Übersicht Grafik: ts 33 Streamorientierte Kommunikation Problem: Wenig Steuer-Fluss, dafür ein hohes Datenaufkommen, meist in eine Richtung. Bei den Daten werden QoS (Quality of Service)-Anforderungen gestellt. Filme dürfen nicht ruckeln, 30 Frames/sec. Audio muss kontinuierlich übertragen werden: PCM 1/44100 sec. Intervalle bei Playback. Die streamorientierte Kommunikation lässt sich nicht mit den bisher besprochenen Middleware-Techniken implementieren, bei denen Zeit keine Rolle spielt. 34 Streamorientierte Übertragungsarten Asynchrone Übertragung Synchrone Übertragung Eine maximale End-To-End-Verzögerung ist definiert, die Daten dürfen aber auch schneller übertragen werden. Isochrone Übertragung Beispiel: File-Transfer Die Daten werden ohne Zeitbedingungen sequentiell übertragen. Es gibt eine minimale und maximale End-to-End-Verzögerung, sog. Beschränkter Jitter. Dies ist eine wichtige Übertragungsart für Mulitmedia. Einfache Streams: nur eine Quelle für Daten, z.B. Sprache. Zusammengesetzte Streams: mehrere Quelle für Daten, z.B. lippensynchrone Übertragung von Ton und Bild. 35 VS: Verteilte Systeme Prof. Dr. Fritz Jobst FH Regensburg FB Informatik/Mathematik Sockets in Java Architektur von Socket-Lösungen 36 Inhalt Sockets und Ports System-unabhängige Datendarstellung in Java Die Klassen DataInputStream und DataOutputStream Abbildung auf Streams (Æ TCP) Beispiel: Additions-Server für TCP Verbindungsorientierte Sockets Verbindungsfreie Sockets Architektur Kurzbeschreibungen für die Klassen Socket, ServerSocket InetSocketAddress, URL 37 Sockets: Allgemein Das Socket-Interface wurde zur Programmierung verteilter Anwendungen auf TCP/IP-Netzwerken für UNIX-Rechner entwickelt. Die Socket-Abstraktion setzt zwei Prozesse voraus, die auch auf demselben Rechner laufen können. Ein Socket ist dabei als ein Endpunkt einer Kommunikation im Netzwerk zu verstehen. Um eine Verbindung zu ermöglichen, braucht man zwei solcher Enden (Æ port) Dabei gibt es zwei Typen von Verbindungen verbindungsfrei: Datagramme, einzelne Pakete von Nachrichten (UDP) verbindungsorientiert: sequenzielle Bearbeitung in Streams möglich (TCP) 38 Sockets und Ports socket any port agreed port socket message client server other ports Internet address = 138.37.94.248 Internet address = 138.37.88.249 Grafik: cdk 39 Kommunikation mit verbindungsfreien Sockets Mit verbindungsfreien Sockets können einzelne Datenpakete versandt werden. Transfer Der empfangende Prozess muss ein Byte-Array zur Verfügung stellen, in das die Nachricht passt. Ist die Nachricht zu lang, so wird sie gekürzt. Senden (send) blockiert nicht, empfangen (receive) blockiert, außer wenn man einen Time-Out angibt. Die Empfangsmethode receive nimmt jede Nachricht an, die an den Socket adressiert ist. Der Absender kann aus der Nachricht ausgelesen werden. Fehlermöglichkeiten Jedes Paket ist unabhängig von den anderen. Pakete können verloren gehen. Einzelne Pakete können andere Pakete überholen. 40 Verbindungsfreie Sockets in Java: Übersicht Client2 Server1 DatagramSocket DatagramPacket Client1 DatagramSocket DatagramSocket DatagramPacket DatagramPacket Server2 DatagramSocket Jedes DatagramPacket kann an einen anderen Server geschickt werden! DatagramPacket 41 Aufbau eines Clients (UDP) nach cdk import java.io.*; import java.net.*; public class UDPClient { public static void main(String args[]) { // args give message contents and server hostname DatagramSocket aSocket = null; try { aSocket = new DatagramSocket(); byte[] m = args[0].getBytes(); InetAddress aHost = InetAddress.getByName(args[1]); int port = 6789; DatagramPacket request=new DatagramPacket(m, m.length, aHost, port); aSocket.send(request); byte[] buffer = new byte[1000]; DatagramPacket reply = new DatagramPacket(buffer, buffer.length); aSocket.receive(reply); System.out.println("Reply: " + new String(reply.getData())); } catch (SocketException e) { System.out.println("Socket: " + e.getMessage()); } catch (IOException e) { System.out.println("IO: " + e.getMessage()); } finally { if (aSocket != null) aSocket.close(); } } } 42 Aufbau eines Servers (UDP) nach cdk import java.io.IOException; import java.net.*; public class UDPServer { public static void main(String args[]) { DatagramSocket aSocket = null; try { aSocket = new DatagramSocket(6789); byte[] buffer = new byte[1000]; while (true) { DatagramPacket request = new DatagramPacket(buffer, buffer.length); aSocket.receive(request); DatagramPacket reply = new DatagramPacket(request.getData(), request .getLength(), request.getAddress(), request.getPort()); aSocket.send(reply); } } catch (SocketException e) { System.out.println("Socket: " + e.getMessage()); } catch (IOException e) { System.out.println("IO: " + e.getMessage()); } finally { if (aSocket != null) aSocket.close(); } } } 43 Verbindungsfreie Sockets und Daten in Java Problem: Datagramme senden bzw. empfangen nur BytePuffer: byte[]. Die Daten sollten mit den Klassen DataInputStream bzw. DataOutputStream gelesen bzw. geschrieben werden. Zum Senden können die Daten in einen DataOutputStream für einen ByteArrayOutputStream geschrieben werden. Dieser Stream wird dann als Byte-Array ausgelesen und geschrieben. Analog: Man empfängt einen Byte-Array. Dieser wird als ByteArrayInputStream an einen DataInputStream übergeben und von da mit DataInputStream ausgelesen. Strings: readUTF bzw. writeUTF benutzen. 44 UNIX-BSD-Sockets: Abläufe Server socket() bind() Client listen() socket() accept() connect() recv() send() send() recv() 45 Verbindungsorientierte Sockets 1 Bei der Verbindung zweier Prozesse sind die Rollen von Client und Server festgelegt. Jeder Socket hat einen Ausgabe- und einen Eingabestream. Senden erfolgt durch Schreiben in den Stream, Empfangen durch Lesen aus dem Stream. Der Schreiber kann blockiert werden, wenn er für den Leser zu schnell ist. Schließen eines Sockets: Daten werden an den Empfänger gesendet. Kennzeichen über das Schließen der Verbindung wird gesendet. Beim Ende eines Prozesses werden alle seine Sockets geschlossen. 46 Verbindungsorientierte Sockets 2 Fehlerbehandlung Prozesse können nicht zwischen Netzwerkfehlern und Fehlern in den Partner-Prozessen unterscheiden. Die kommunizierenden Prozesse erkennen nicht, ob ihre letzten Nachrichten empfangen wurden oder nicht. Sockets und Prozesse, Threads Das Empfangen per receive blockiert einen Prozess. Damit würde ein Server-Prozess blockiert und könnte keine weiteren Anfragen bearbeiten: sog. Sequentieller Server. Delegiert man die Bearbeitung einzelner eingehender Verbindungen an Threads, dann kann man weitere Anfragen auch während des Wartens bearbeiten: paralleler Server. 47 Verbindungsorientierte Sockets in Java: Übersicht Server ServerSocket: accept Socket InputStream OutputStream Client Socket OutputStream InputStream 48 Verbindungsorientierte Sockets in Java In Java ist der Zugriff auf verbindungsorientierte Sockets mit den Klassen Socket und ServerSocket gekapselt. Der Programmierer ist von den Niederungen spezieller Implementierungen auf den verschiedenen Architekturen völlig entlastet. Die Anlage einer Instanz der Klasse ServerSocket beinhaltet alle Funktionen zum Anlegen eines Server-Sockets. accept liefert dann einen Socket für eine am Server eingehende Verbindung, die ein Client durch Anlage eines Sockets eröffnet hat. Die Verbindung liefert Streams zum Schreiben bzw. zum Lesen. Daten sollten mit DataInputStream gelesen bzw. mit DataOutputStream geschrieben werden. 49 Aufbau einer Client-Anwendung (TCP) try { Socket client = new Socket (Host, PortNummer); InputStream in = client.getInputStream (); OutputStream out = client.getOutputStream (); // Lesen von in: ggfs Reader benutzen // Schreiben nach out. ggfs. PrintWriter benutzen ....... // Schließen der Streams und der Verbindung } catch (IOException ioe) { // Fehlerbehandlung } 50 Aufbau eines sequenziellen Servers (TCP) try { ServerSocket server = new ServerSocket (PortNummer); while (true) { Socket verbindung = server.accept (); InputStream in = verbindung.getInputStream (); OutputStream out = verbindung.getOutputStream (); // Lesen von in und Schreiben nach out ....... // Schließen der Streams und der Verbindung // Herunterfahren des Servers ? -> Schleife verlassen } } catch (Exception e) { // Fehlerbehandlung für // IOException, SecurityException, // SocketTimeoutException, IllegalBlockingModeException } // Schließen des Servers 51 Aufbau einer parallelen Servers (TCP) Eine parallele Server-Anwendung unterscheidet sich von einer sequenziellen Anwendung dadurch, dass bei jeder mit accept() angenommenen Verbindung ein Thread zu deren Bearbeitung gestartet wird. Der Server bearbeitet die Verbindung nicht mehr weiter, sondern horcht wieder am Socket, ob weitere Verbindungen eingehen. Der Serverbetrieb muss dann – wie beim sequenziellen Server – am Server durch Abbruch des Java-Programms oder von Clients durch spezielle Kommandos beendet werden. Parallele Server sind in Java mit einem gegenüber sequenziellen Servern kaum erhöhten Aufwand zu realisieren. Die Software-Struktur gewinnt durch die Trennung zwischen dem Aufbau der Verbindung und der Bearbeitung der Verbindung an Transparenz. 52 Parallele Server in Java Teil 1 class ServerThread extends Thread { Socket verbindung = null; ServerThread (Socket verbindung) { this.verbindung = verbindung; } void run () { try { InputStream in = verbindung.getInputStream (); OutputStream out = verbindung.getOutputStream (); // Lesen von in und Schreiben nach out .. ..... // Schließen der Streams und der Verbindung } catch (IOException ioe) { // Fehlerbehandlung } } } 53 Parallele Server in Java Teil 2 // Server-Klasse class Server { // ... irgendeine Methode fuer die Arbeit im Server ServerSocket server = new ServerSocket (PortNummer); Socket verbindung = null; while (true) { try { verbindung = server.accept (); } catch (Exception e) { // IOException, SecurityException, // SocketTimeoutException, IllegalBlockingModeException } ServerThread serverthread = new ServerThread (verbindung); serverthread.start (); // Herunterfahren des Servers ? -> Schleife verlassen } // Schließen des Servers 54 System-unabhängige Datendarstellung in Java Hierzu dienen in Java die Klassen DataInputStream und DataOutputStream. Diese Klassen enthalten Methoden zum Lesen und Schreiben von Daten der elementaren Datentypen Strings können in einem modifizierten UTF-8-Format gelesen bzw. geschrieben werden. boolean, byte, char, short, int, long, float, double readBoolean, readByte, readChar, readShort, readInt, readLong, readFloat, readDouble writeBoolean, writeByte, writeChar, writeShort, writeInt, writeLong, writeFloat, writeDouble readUTF writeUTF Zur Serialisierung von Objekten kann man auch die Methoden writeObject von ObjectOutputStream bzw. readObject von ObjectInputStream benutzen. 55 Die Socket-Pipeline writeInt … Sender readInt … Internet Dataoutput -Stream Socket Datainput -Stream Empfänger 56 Anbindung der Data…- Streams an TCP Grundsätzliche Konstruktion in Java InputStream is = … Irgendein InputStream OutputStream os = … Irgendein OutputStream DataInputStream dis = new DataInputStream(is); DataOutputStream dis = new DataOutputStream(os); Der Input- bzw. OutputStream kann bei verbindungsorientierten Sockets aus dem Socket ausgelesen werden: Client (TCP) InputStream in = client.getInputStream (); OutputStream out = client.getOutputStream (); Server (TCP) Socket verbindung = server.accept (); InputStream in = verbindung.getInputStream (); OutputStream out = verbindung.getOutputStream (); 57 Anbindung der Data…- Streams an UDP Problem: Datagramme (UDP) senden bzw. empfangen nur Byte-Puffer. Diese Byte-Puffer müssen an Streams angebunden werden. Schreiber-Lösung in Java mit ByteArrayOutputStream ByteArrayOutputStream bos = new ByteArrayOutputStream(); DataOutputStream dos = new DataOutputStream(bos); dos.writeInt(i); dos.writeInt(j); // Die Daten in ein Byte-Array auslesen byte[] buf = bos.toByteArray(); … Damit kann das „DatagramPacket“ geschnürt werden Leser-Lösung in Java mit ByteArrayInputStream // Daten als Byte-Array aus dem Paket holen byte[] data = packet.getData(); ByteArrayInputStream bis = new ByteArrayInputStream(data); DataInputStream dis = new DataInputStream(bis); int x3 = dis.readInt(); 58 Beispiel: Additions-Service Ein Server bietet die Addition ganzer Zahlen als Dienst an. Die Schnittstelle ICalc beschreibt die Aufgabe, CalcImpl implementiert den Dienst. public interface ICalc { int add(int i, int j); } public class CalcImpl implements ICalc { public int add(int i, int j) { return i + j; } } Aufgabe: Implementierung einer Anbindung für Clients, um Aufträge an den Server abzusetzen. Die Ergebnisse sollen entgegengenommen und an den Client zurückgegeben werden. Implementierung einer Anbindung für den Server, um Aufträge entgegenzunehmen, die Parameter zu auszupacken, den „eigentlichen“ Server (s. o.) aufzurufen, die Ergebnisse zu verpacken und zurückzusenden. 59 Architektur der Lösung Die Lösung erfolgt für Sockets mit TCP bzw. UDP Proxy Server lokaler Stellvertreter. Parameter auf den Kommunikationskanal schreiben. Server aufrufen. Ergebnisse lesen und an den Auftraggeber zurückliefern. Der Server wartet am Socket auf Aufträge. Für jeden Auftrag wird ein ServerThread gestartet. ServerThread Eingehende Daten übernehmen. Über den Adapter die Implementierung aufrufen. Æ Der Adapter passt die Implementierung an Streams an. Ergebnisse zurück an den Auftraggeber (Æ Proxy) senden. 60 Grobstruktur der Lösung Client 1 Server 8 2: Stream bzw. Paket ServerThread Proxy 7: Stream bzw. Paket 3 6 CalcAdapter •Pakete bzw. Streams enthalten binäre Daten. •Zugriff mit DataInputStream bzw. DataOutputStream. •CalcAdapter passt die Implementierung einer Schnittstelle an solche Datenströme an. 4 5 CalcImpl 61 Benutzte Patterns Stellvertreter oder Proxy Adapter Hier wird speziell ein Remote-Proxy benutzt. Der lokale Stellvertreter nimmt Anfragen entgegen und leitet sie zur Bearbeitung weiter. http://de.wikipedia.org/wiki/Stellvertreter_%28Entwurfsmuster%29 Eine Klasse passt eine Schnittstelle x an eine andere Schnittstelle y an. http://de.wikipedia.org/wiki/Adapter_%28Entwurfsmuster%29 Beide Patterns stammen aus dem Buch der GoF (Gang of Four) Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides: Entwurfsmuster. Elemente wiederverwendbarer objektorientierter Software. Addison Wesley, Bonn 1996 ISBN 3-89319-950-0 62 Beispiel: Client Ein Client kann den Dienst anfordern Ein lokaler Stellvertreter (Proxy) implementiert den Dienst. import middleware.*; public class Client { public static void main (String[] args) { ICalc proxy = new Proxy(args[0]); int i = Integer.parseInt(args[1]); int j = Integer.parseInt(args[2]); int k = proxy.add(i, j); System.out.println(k); } } 63 Sockets: TCP oder UDP? Der lokale Stellvertreter (Proxy) nimmt den Auftrag entgegen. Er kommuniziert mit dem Server entweder über TCP oder über UDP. In jedem Fall müssen die Daten in einer plattformunabhängigen Darstellung übergeben werden. Dies besorgen die Klassen DataInputStream bzw. DataOutPutStream aus dem Package java.io. Dieser Teil der Arbeit ist im Server für TCP und UDP identisch und wird von der Klasse CalcAdapter geleistet. CalcAdapter passt die Schnittstelle ICalc an die DataInputbzw. DataOutputStreams an. 64 CalcAdapter package middleware; import java.io.*; public class CalcAdapter { public void handleRequest (DataInputStream dis, DataOutputStream dos) { // I/O Streams vom Socket try { Class c = Class.forName("CalcImpl"); middleware.ICalc server = (middleware.ICalc) c.newInstance(); int x1 = dis.readInt(); int x2 = dis.readInt(); int x3 = server.add(x1, x2); System.out.println("x1 = " + x1 + " x2 = " + x2); dos.writeInt(x3); } catch (Exception e) { System.out.println(" " + e); } } } 65 TCP: Proxy (1/2) package middleware; import java.io.*; import java.net.*; public class Proxy implements ICalc { private Socket client = null; private DataOutputStream dos = null; private DataInputStream in = null; private String host = null; public Proxy(String host) { this.host = host; } void closeAll() { ... } 66 TCP: Proxy (2/2) public int add(int i, int j) { try { client = new Socket (host, port); // Verbinde die Streams dos = new DataOutputStream (client.getOutputStream()); dos.writeInt(i); dos.writeInt(j); // Auslesen der Antwort des Servers in = new DataInputStream (client.getInputStream()); int result = in.readInt(); closeAll(); return result; } catch (IOException e) { } return 0; } 67 TCP: Server (1/2) package middleware; import java.net.*; public class Server { public static void main(String args[]) { ServerSocket s = null; // Socket zum Horchen Socket con = null; // Eingehende Verbindung // Server-Socket an port 8011 oeffnen try { s = new ServerSocket(8011); String n = s.getInetAddress().toString(); } catch (Exception e) { System.out.println("Exception:\n" + e); System.exit(1); // Herunterfahren des Servers } 68 TCP: Server (2/2) while (true) { // Warte auf eingehende Verbindung try { con = s.accept(); System.out.println("accept : " + con); Thread thread = new middleware.ServerThread(con); thread.start(); } catch (Exception e) { System.out.println("Fehler : " + e); System.exit(1); // Abbruch des Server-Betriebs } } } } 69 TCP: ServerThread (1/2) package middleware; import java.io.*; import java.net.*; public class ServerThread extends Thread { private Socket con = null; // Connection private DataInputStream in = null; private DataOutputStream out = null; void closeAll() { ... } 70 TCP: ServerThread (2/2) public void run() { // I/O streams vom Socket try { in = new DataInputStream(con.getInputStream()); out = new DataOutputStream(con.getOutputStream()); CalcAdapter ca = new CalcAdapter(); ca.handleRequest(in, out); } catch (Exception e) { System.out.println(" " + e); } closeAll(); } public ServerThread(Socket con) { this.con = con; } } 71 Kurzinfo zu der Klasse Socket Socket(String host, int port) throws IOException, UnknownHostException; Erzeugt einen Stream-Socket und verbindet ihn zum angegebenen Port auf dem angegebenen Host. Socket(InetAddress address, int port) throws IOException; Erzeugt einen Stream-Socket und verbindet ihn zum angegebenen Port auf der angegeben Adresse. void close() throws IOException; Schließt den Socket. InputStream getInputStream() throws IOException; Gibt einen InputStream für diesen Socket zurück. Dieser kann wie in Kap. 5 beschrieben bearbeitet werden. InetAddress getLocalAddress(); Liefert die lokale Adresse, an die der Socket gebunden ist. OutputStream getOutputStream() throws IOException; Gibt einen OutputStream für diesen Socket zurück. Dieser kann wie in Kap. 5 beschrieben bearbeitet werden. boolean isClosed(); genau dann wahr, falls Socket geschlossen. boolean isConnected(); genau dann wahr, falls Socket verbunden. 72 Kurzinfo zu der Klasse ServerSocket ServerSocket() throws IOException; Erzeugt einen ungebundenen Server-Socket. ServerSocket(int port) throws IOException; Erzeugt einen Server-Socket an dem angegebenen Port. Wenn 0 angegeben ist, wird ein beliebiger freier Port gewählt. Auch eine SecurityException ist möglich. Socket accept(); Horcht auf eine an diesem Socket eingehende Verbindung und akzeptiert sie. void close(); Schließt diesen Socket. InetAddress getInetAddress(); Gibt die lokale Adresse des Server-Sockets zurück boolean isBound(); Liefert den Bindungs-Status des Server-Sockets. boolean isClosed(); genau dann wahr, falls Socket geschlossen. 73 Kurzinfo zu der Klasse InetSocketAddress InetSocketAddress (InetAddress addr, int port); Erzeugt eine Socket-Adresse aus ei-ner IP-Adresse und einer Port-Nummer. InetSocketAddress (String hostname, int port) Erzeugt eine Socket-Adresse aus einem Hostnamen und einer Port-Nummer. InetAddress getAddress(); Liefert die Internet-Adresse. String getHostName(); Liefert den Hostnamen. int getPort(); Liefert die Port-Nummer. 74 Kurzinfo zu der Klasse URL URL(String protocol, String host, int port, String file) throws MalformedURLException; Erzeugt eine URL aus dem Protokoll, Host, Port und Datei. URL(String spec) throws MalformedURLException; Erzeugt eine URL aus einer URL-spezifikation in Textform. String getQuery(); Liefert den Abfrageanteil einer URL (d.h. die Zeichenfolge nach dem Fragegezeichen ‚?’). String getPath(); Liefert den Pfad einer URL. int getPort(); Liefert den Port einer URL. String getProtocol(); Liefert das Protokoll einer URL. String getHost(); Liefert den Host einer URL. Das Format entspricht RFC 2732, d.h. für eine IPv6-Adresse wird die Adresse in Klammern ('[' und ']') geliefert. String getFile();. Liefert den Dateinamen dieser URL InputStream openStream() throws IOException; Liefert einen Eingabestrom, um von der URL zu lesen. 75