4 Client/Server-Anwendungen mit TCP Das wichtigste Protokoll der Transportschicht im TCP/IP-Schichtenmodell ist das Transmission Control Protocol (TCP). Es ist aufwändiger als DDP. stellt aber dafür eine verlässliche Verbindung zwischen Client und Server her. Viele bekannte IntemetDienste wie PTP (File Transfer Protocol), Telnet, SMTP (SimpleMail Transfer Protocol), POP (Post Office Protocol) und HTTP(Hypertext Transfer Protocol) nutzen TCP. 4.1 Das Protokoll TCP TCP ist im Gegensatz zu UDP ein verbindungsorientiertes Protokoll. Zwischen zwei Prozessen (Client und Server) wird eine virtuelle Verbindung hergestellt. über die in Form eines bidirektionalen Datenstroms Bytefolgen beliebiger Länge geschickt werden können. Vor der übertragung von Daten muss der Client eine Verbindung zum Server anfordern. Wird die Verbindung nicht mehr gebraucht, fordert einer der beteiligten Prozesse TCP auf, sie abzubauen. ~ () ( An f rage ( Client 1Il An tw or t ( ) Bidirektionale TCP-Verbindung ~ Server TCP sorgt für die Aufteilung der Daten in einzelne Pakete. garantiert . dass die Pakete den Empfänger in der richtigen Reihenfolge erreichen, und initiiert die Neuübertragung von verloren gegangenen oder defekten Paketen. TCP ist im RFC 793 der IETF spezifiziert (siehe auch http://www . i etf .org I rfel rfe 793. t xt) , Bild 4.2 zeigt den Aufbau eines TCP-Pakets. das Bestandteil der Nutzdaten des IPv4-Pakets ist. Der TCP-Kopfteil enthält. • die Portnummer des Senders (2 Byte). • die Portnummer des Empfängers (2 Byte). • weitere Felder mit einer Gesamtlänge von 16 Byte wie z. B. Reihenfolgenummer und Prüfsumme, Bild 4.1' TCP-Verbindung 4 118 • Client/Server-Anwendungen mit TCP optionale Felder variabler Länge. Darauf folgen dann die eigentlichen Nutzdaten des TCP-Pakets. i Bild 42.· Aufbau des TCPPakets !4 ----r--- ,I IP-Adresse des Senders IPv4-Kopfteil -+ IP-Adresse des Empfängers · .. Portnummer des Senders TCP-Kopfteil --+---- TCP-Nutzdaten 4.2 Client-Socket und Server-Socket --r----- · .. I 20 Byte J . --r----0- 40 Byte Portnummer des Empfängers 20 Byte · .. optionale Felder Nutzdaten TCP-Sockets 1m Gegensatz zu DDP wird bei TCP zwischen Client-Socket und Server-Socket unterschieden. Der Server-Socket versetzt den Server in die Lage , die Verbindungsanforderungen der Clients abzuhören und dann mehrere Clients zu bedienen. Bevor Daten zwischen Client und Server ausgetauscht werden können, muss eine Verbindung zwischen Sockets hergestellt werden. Der Aufruf der Methode accept des Server-Sockets blockiert den Server so lange, bis ein Client versucht, Kontakt aufzunehmen. accept liefert als Ergebnis einen weiteren Socket, über den dann die eigentliche Kommunikation stattfindet. Die Kontaktaufnahme auf der Client-Seite geschieht automatisch bei der Erzeugung des Client-Sockets mit einem geeigneten Konstruktor. Eine Verbindung kann natürlich nur hergestellt werden, wenn der Server zeitlich vor dem Start des Client accept aufgerufen hat. Über Ein- und Ausgabeströme können nun Daten empfangen bzw. gesendet werden. Die Verbindung wird abgebaut, wenn einer der beiden Sockets geschlossen wird. 4.2 TCP-Sockets Die Klasse java. net Socket implementiert einen Client-Socket. 119 Socket Socket(InetAddress address. int port) throws IOException erzeugt einen Client-Socket und bindet ihn an die vorgegebene IP-Adresse und Portnummer. Socket(String host. int port) throws java.net.UnknownHostExceptlon, IOExcept l on erzeugt einen Client-Socket und bindet ihn an den vorgegebenen Rechner und an die vorgegebene Portnummer. void close() throws IOException schließt den Socket. boolean isClosed() liefert t rue, falls der Socket geschlossen ist. sonst fa 1se. vOld setSoTimeout(lnt tl meout) throws SocketExceptlon setzt ein Timeout in Millisekunden. read für den Eingabestrom dieses Sockets blockiert höchstens ti rre out Millisekunden und löst dann die Ausnahme ja va.net.SocketTlmeoutExceptlon aus. Wird tlmeout = 0 gesetzt, so wird die Timeout-Steuerung deaktiviert. int getSoTimeout() throws SocketException liefert die Timeout-Angabe. InputStream getlnputStream() throws IOException liefert einen Eingabestrom für diesen Socket. Input / Output OutputStream getOutputStream() throws IOExcept i on liefert einen Ausgabestrom für diesen Socket. Wird einer der beiden Datenströme mit der entsprechenden close-Methode geschlossen. so wird auch der zugehörige Socket geschlossen. Mit einer der folgenden Methoden kann jeweils nur einer der beiden Datenströme (Eingabe bzw. Ausgabe) geschlossen werden. ohne damit den Socket selbst zu schließen, void shutdownlnput() throws IOExcept i on setzt den Eingabestrom für diesen Socket auf EOF (End Of File). void shutdownOutput() throws IOExcept i on deaktiviert den Ausgabestrom für diesen Socket. Die Klasse java .net. ServerSocket implementiert einen Seruer- SeroerSocket Socket. ServerSocket(int port) throws IOExcept i on erzeugt einen Server-Socket und bindet ihn an die vorgegebene Portnummer. void close() throws IOExcept i on schließt den Server-Socket, 4 120 Client/Server-Anwendungen mit TCP boolean isClosed() liefert true, falls der Server-Socket geschlossen ist, sonst f al se. Socket accept() throws IOException nimmt einen Verbindungswunsch an und erzeugt einen neuen Socket für diese Verbindung, accept blockiert so lange, bis eine neue Verbindung aufgenommen "WUrde. vOld setSoTimeout(int timeout) throws SocketException setzt ein Timeout in Millisekunden. accept blockiert höchstens timeout Millisekunden und löst dann die Ausnahme java. net. SocketTimeoutException aus. Wird timeout = 0 gesetzt, so wird die Timeout-Steuerung deaktiviert. int getSoTimeout() throws IOException liefert die Timeout-Angabe. backlag Verbindungswünsche. für die mittels accept noch kein Socket erzeugt werden konnte, werden in einer Warteschlange (backlag) verwaltet Falls die Warteschlange voll ist und ein neuer Verbindungswunsch eingeht, wird beim Client die Ausnahme java. net. ConnectExcepti on ausgelöst. Die maximale Länge der Warteschlange ist standardmäßig 50, Mit dem Konstruktor ServerSocket(int port, int backlog) throws IOException kann die maximale Länge der Warteschlange explizit festgelegt werden. AblauJschritte 1. Der Server erzeugt ein ServerSocket-Objekt, das an einen vorgegebenen Port gebunden wird, 2, Der Server ruft die Methode accept des Server-Sockets auf und wartet auf den Verbindungswunsch eines Client. 3, Der Client erzeugt ein Socket-Objekt mit der Adresse und der Portnummer des Servers und versucht damit, eine Verbindung zum Server aufzunehmen. 4. Der Server erzeugt ein Socket-Objekt, das das serverseitige Ende der Kommunikationsverbindung darstellt Die Methode accept gibt dieses Objekt zurück 5, Um eine bidirektionale Verbindung zwischen Client und Server aufzubauen, stellen Client und Server jeweils einen Ausund Eingabestrom mit Hilfe ihrer Socket-Objekte bereit 6. Nun können Daten mittels Lese- und Schreiboperationen hinund hergeschickt werden, 4.3 Ein Echo-Server und -Client 121 Bild 4.3 zeigt den Ablauf beim Aufbau einer TCP-Verbindung. Bild 43· Aufbau einer TCPVerbindung ServerSocket I accept() <E- .:~~i2!. ~ ! getlnputStream() I clienlSockel !, ------------------+----------~ gelOJtputStream() ! I getlnputStream() getOJtputStream() -----------------+---------1 Die beiden Sockets sind miteinander verbunden. Weitere Socket-Methoden sind, InetAddr es s getLocalAddress() liefert die lokale IP-Adresse, an die der Socket gebunden ist. SocketInformationen int getLocalPort() liefert die lokale Portnummer, an die der Socket gebunden ist. Inet Addre s s getInetAddress() liefert die entfernte IP-Adresse, mit der der Socket verbunden ist. i nt getPort() liefert die entfernte Portnummer, mit der der Socket verbunden ist. 4.3 Ein Echo-Server und -Client Wir stellen nun eine verbindungsorientierte Version des EchoDienstes vor. Der Client schickt in einer Schleife jeweils eine Textzeile zum Programm 41 Server, der diese dann wieder an den Client zurückschickt. Die Eingabe erfolgt über Tastatur. Hier ist zunächst der Client: 4 122 EchoClient Client/Server-Anwendungen mit TCP Die eigentliche Verarbeitung findet in einer try-Anweisung statt. Die Verbindung zum Server wird am Ende mit socket.close() abgebaut. lmport lmport lmport lmport lmport java java java java java lo.BufferedReader; lo.IOExceptlon; lo.InputStreamReader; lo.PrlntWrlter; net.Socket; public class EchoClient ( public static voi d main(String[] args) { Socket socket ~ null; try ( String host ~ args[O]; int port ~ Integer.parselnt(args[ l]); socket ~ new Socket(host. port); BufferedReader in ~ new BufferedReader( new InputStreamReader(socket.getlnputStream())); PrlntWrlter out = new PrlntWrlter(socket .getOutputStream(). true); BufferedReader input ~ new BufferedReader( new InputStreamReader(System. in)); String msg ~ in.readLine(); System.out.println(msg); Strlng I i ne : whi le (true) ( line ~ input.readLine(); if (line ~~ null 11 line.equals("q")) break; out.println(line); System.out.println(in readLine()); } in.close(); out.close() ; input.close() ; catch (Exception e) ( System.err.println(e); finally { try { i f (socket !~ null) socket.close(); catch (IOException e) 4.3 Ein Echo-Server und -Client 123 Zunächst wird ein Client-Socket erzeugt und damit versucht, die Verbindung zum Server herzustellen. Das BufferedReader-Objekt in ist der Eingabestrom. das Pri nt Wri ter-Objekt out der Ausgabestrom für diesen Socket. Dann wird eine Nachricht des Servers ausgegeben. In einer Schleife werden nun von der Tastatur Textzeilen eingelesen, in den Ausgabestrom O ll t geschrieben und damit zum Server geschickt. in. readLi ne () blockiert so lange. bis die Antwort vom Server vorliegt. Diese wird dann angezeigt. Wir zeigen zwei Server-Versionen. Die erste Version kann nicht mehrere Clients gleichzeitig bedienen. Werden z.B. zwei Clients kurz hintereinander gestartet, so werden sie der Reihe nach bedient. Erst wenn der erste Client beendet "WUrde, kommt der zweite zum Zuge. Es handelt sich also um einen so genannten iterativen Server. lmport lmport lmport lmport lmport lmport lmport java. ia Buf feredReader ; java. io. IOExceptlon; j ava. r o . InputSt reamReader ; java. lo.PrlntWriter; java net. InetAddress; java net.Ser verSocket; java net.Socke t; publlc class EchoSer verl prl vate lnt port; private int backlog; public EchoSer ver l( i nt port. i nt backlog) ( this.port ~ port; this.backlog ~ backlog ; public void startServer() { t ry ( ServerSocket server new ServerSocket (port, backlog); = Inet Addr es s addr ~ Inet Address .get Local Hos t( ); System.out.println (" EchoServer 1 auf" + addr. get HostNarre () + "/" + addr. getHostAddress () + ".;" + port + " gestartet ... "); process(server); catch (IOException e) System.err.println (e); EchoServerl 4 124 Client/Server-Anwendungen mit TCP prlvate vOld process(ServerSocket server) throws IOException { whi le (true) ( Socket cllent = server.accept(); String clientAddr ~ client.getInetAddress() .getHostAddress(); int clientPort ~ client.getPort(); System.out.println("Verbindung zu " + clientAddr + ".;" + cllentPort + " aufgebaut"); try ( BufferedReader in ~ new BufferedReader( new InputStreamReader(client.getInputStream())); PrlntWrlter out = new PrlntWrlter(cllent .getOutputStream(). t r ue ) ; out pr i nt ln ("Server l st berelt .. . "); String input; while ((input ~ in.readLine()) out. pri nt 1n(i nput); !~ null) { in.close(); out.close() ; catch (IOException e) System.err.println(e); finally { try { if (client !~ null) cl i ent. cl ose(); catch (IOException e) System.out.println("Verbindung zu " + cl i entPort + " abgebaut"); + clientAddr + ";" public static void main(String [] args) { if (args.length !~ 1 && args.length !~ 2) System.err .println("java EchoServer1 <port> [<backlog>]"); System.exit(l); int port ~ Integer parse Int(args[O]); int backlog ~ 50; 4.3 Ein Echo-Server und -Client 125 i f (args.length ~~ 2 ) backlog ~ Int eger . pars el nt (args [l ] ) : new EchoSer ver 1(port . backlog).startServer(): Der Server-Socket wird erzeugt und an die vorgegebene Portnummer des lokalen Rechners gebunden. Hier wird auch die maximale Länge der Warteschlange (backlag) vorgegeben. Somit kann das Verhalten der Anwendung mit unterschiedlichen Werten getestet werden. Je Schleifendurchgang in der Methode process erfolgen diese Schritte; • Aufruf der Methode accept, die so lange blockiert, bis ein Client versucht, Verbindung aufzunehmen. • Bereitstellung der Ein- und Ausgabesträme 'in und out. • Sendung einer Nachricht an den Client. • In einer Schleife werden Textzeilen eingelesen und sofort in den Ausgabestrom geschrieben. Die Schleife läuft so lange, bis der Client die Verbindung und damit seinen Ausgabestrom geschlossen hat. • Der Socket wird geschlossen. Aufruf des Servers; Test start ja va -cp bin EchoSer ver1 50000 Aufruf des Client: java -cp bin EchoCl i ent loca lhost 50000 Der Server meldet den Auf- und Abbau der Verbindung; EchoSer ver1 auf abts-PC/ 192. 168.2 . 100:50000 gestarte t Verbindung zu 127. 0. 0.1 :49333 aufgebaut Verbindung zu 127. 0. 0.1 :49333 abgebaut Der Server kann über Tastatur mit Strg+C abgebrochen werden. Die zweite Version des Echo-Servers ermöglicht die parallele EchoServer2 Bedienung mehrerer Clients. Dazu erfolgt die Kommunikation mit einem Client innerhalb eines Threads. 4 126 lmport lmport lmport lmport import import import ja va ja va ja va ja va ja va ja va ja va Client/Server-Anwendungen mit TCP lo.BufferedReader; lo. IO Exceptlon; lo. InputStreamReader; lo.PrintWriter; net. InetAddress; net.ServerSocket; net.Socket; public class EchoServer2 private i nt port; publ i c EchoSer ver2(int port) ( this port ~ port; public void startServer() { try ( ServerSocket server = new ServerSocket(port); Inet Address addr ~ Inet Address .get Local Host ( ) ; System.out.println("EchoServer2 auf" + addr. getHostName() + "/" + addr. getHostAddress() + '";" + port + " gestartet ... "); whi le (true) ( Socket client = server.accept(); new EchoThread(cl i ent ) .st art ( ) ; } catch ( IOException e) System.err.pri ntln(e); priv at e class EchoThread extends Thread ( pri vate Socket cli ent; public EchoThread(Socket client) ( this client = client; public voi d run() ( String clientAddr ~ client get lnetAddress() .getHostAddress(); int clientPort ~ client.getPort(); System.out.println("Verbindung zu " + clientAddr + ".;" + clientPort + " aufgebaut"); try ( BufferedReader in ~ new Buf feredReader ( new Input St reamReader (cl i ent .getl nput St ream( ) ) ) ; 4.3 Ein Echo-Server und -Client PrlntWrlter out = 127 new PrlntWrlter(cllent .getOutputStream(). true); out pr i nt l n( "Ser ver i s t berel t ... "); String input; while ((input ~ in.readLine()) out.println(input); !~ null) ( in.close() ; out.close() ; catch (IO Exception e) System.err.println(e); finally { try { if (client !~ null) cl ien t.close(); catch (IOExcep tion e ) System.out.println("Verbindung zu " + clientAddr + '";" + clientPort + " abgebaut"); public static voi d main(String[] args) int port ~ Int eger .par sel nt (ar gs [ O] ) ; new Ec hoSe r ver 2(port ) . st ar t Ser ve r ( ) ; Sobald die Methode accept einen Verbindungswunsch angenommen hat, wird mit Hilfe des gelieferten Sockets ein EchoThread-Ob je kt erzeugt und dieser Thread dann gestartet. Sofort ist der Server wieder bereit, eine neue Verbindung aufzunehmen. Pro Verbindung existiert also ein eigener Thread, der den entsprechenden Client bedient. 4 128 4.4 Programm 4.2 Client/Server-Anwendungen mit TCP Ein Download-Programm Client und Server des folgenden Beispiels ermöglichen das Herunterladen von beliebigen Dateien aus einem vorgegebenen Verzeichnis. Bild 4.4.· Download Kommandos: li s t g et <f i le> q Download FileServer FileClient EBJ Folgende Kommandos kann der Client verwenden: FileSeroer lis t Ausgabe aller Dateinamen des Serververzeichnisses get <fi l e> Download der Datei <fl le> q Beenden des Programms lmport lmport lmport lmport lmport lmport lmport lmport l mport i mport l mport lmport lmport ja va ja va ja va ja va ja va ja va ja va ja va java java java ja va ja va lo.Buffered InputStream; lo. ByteArrayOutputStream; lo. EO FExceptlon; lO. Flle; lO.Flle Fllter; lo. Flle InputStream; l o.Fl le Not FoundExcept l on; l o.IOExcept l on; lO ObjectInputStream; io.ObjectOutp utStream; net. InetAddress; net.ServerSocket; net.Sock et; publlC class Fl l eServer prlvat e lnt port; prl vate Fl l e dl r; public Fi l eServer (i nt port. Fi l e dir) { th is port ~ port; th is dir ~ dir; 4.4 Ein Download-Programm 129 publie void startServer() { try ( ServerSocket server = new ServerSocket(port); InetAddress addr ~ InetAddress.getLoealHost(); System.out.println("FileServer auf" + addr. getHostNarre () + "/" + addr. getHostAddress() + ".;" + port + " gestartet ... "); whi 1e (true) ( Socket cllent = server accept(); new FileThread(elient. dir).start(); } eateh (IOExeeption e) System.err.println(e); private elass FileThread extends Thread ( private final statie long MAX_SIZE ~ 1024 * 1024; // 1 MB private final statie int TIMEOUT ~ 600000; /I 10 Min. private Socket client; private File dir; private ObjectInputStream in; private ObjeetOutputStream out; publie FileThread(Soeket elient. File dir) ( this.client = client; this.dir ~ dir; publie void run() ( String elientAddr ~ elient.getlnetAddress() .getHostAddress(); int elientPort ~ elient.getPort(); System.out.println("Verbindung zu " + elientAddr + '";" + cl i entPort + " aufgebaut"); try ( elient.setSoTirreout(TIMEOUT); out = new ObjectOutputStream( client.getOutputStream()); out. fl ushr ) ; in = new ObjectlnputStream(client.getlnputStream()); whi 1e (true) ( String errd ~ (String) in.readObjeet(); if (emd.equals("list")) ( doList(); } else if (errd.startsWith("get ")) { 4 130 Client/Server-Anwendungen mit TCP String file ~ cmd.substring (4).trim(); doGet(file) ; else if (cmd.equal s ("q" ) ) ( break; in.close(); out.close() ; catch (EOFException e) catch (Exception e) ( System.err.println(e); finally { try { if (client !~ null) cl i ent. cl ose(); catch ( IO Exception e ) System.out.println("Verbindung zu " + cl i entPort + " abgebaut"); + clientAddr + ".;" pri vate void doList() throws IOExcept i on ( // Verzelchnlsnamen werden nlcht zurückgegeben Fi l e[ ] files ~ dir.listFiles(new FileFilter() ( public boolean accept(File pathname) { i f (pathname. is Fi le()) re turn true; else return false; } }) ; out.writeObj ect(files); out .flush(); pri vate void doGet(String file) throws IOExcept i on ( Fi l e f ~ new File(dir. file) ; if (!fisFile()) ( Fi l eNot FoundExcept i on e ~ new FileNotFoundException( fi 1e); out.writeObject(e); out. fl ush() ; return; } if (f.length() Except l on e > = WlX_S IZE) ( new Exceptlon(flle + " i s t zu grass"); out.writeObject(e); 4.4 Ein Download-Programm out. fl ush(); return; BufferedlnputStream fin ~ null; try ( ByteArrayOutputStream bout ~ new ByteArrayOutputStream(); fin ~ new BufferedlnputStream(new FilelnputStream(f)); byte[] b ~ new byte[1024]; lnt c; while ((c ~ fin.read(b)) !~ -1) ( bout.write(b. O. c i: } byte[] bytes ~ bout.toByteArray(); out.writeObject(bytes); catch (IOException e) ( System.err.println(e); finally { try { if (fin !~ null) fin.close(); catch (IOException e) ( public static void main(String[] args) { if (args.length !~ 2) ( System.err.prlntln("java FlleServer <port> <dlr>"); System. exi t (1) ; int port ~ Integer.parselnt(args[O]); File dir ~ new File(args[l]); if (!dir.isOirectory()) { System. err. pri nt ln (args[l] + " ist kei n Verzei chni 5"); System. exi t (1) ; new FileServer(port. dir).startServer(); Der Server wird mit den Parametern Portnummer und Verzeichnis aufgerufen. Zu Beginn wird geprüft, ob der zweite Parameter ein gültiges Verzeichnis bezeichnet. Die Kommunikation mit dem Client findet in einem clientbezogenen Thread statt (run- 131 4 132 Client/Server-Anwendungen mit TCP Methode). Bleibt der Client 10 Minuten inaktiv. wird die Verbindung abgebrochen. Über den Eingabestrom in (vom Typ ObjecUnputStream) werden die Kommandos (Stri ng-Objekte) des Client gelesen. über den Ausgabestrom out (vom Typ ObjectOutputStream) werden Objekte unterschiedlicher Typen (Dateilisten, Byte-Arrays mit dem Dateiinhalt, Exceptions) an den Client gesendet. Achtung Bei der Verwendung von ObjecUnputStream und ObjectOutputStream für die Kommunikation über Sockets ist Folgendes zu beachten, Der ObjectüutputStream-Konstruktor schreibt den so genannten Serialization Stream Header in den Ausgabestrom. Der Obj ecUnputStream-Konstruktor blockiert, bis der zugeordnete Obj ectOutputS tream den Header geschrieben hat. Deshalb sollte unmittelbar nach dem Aufruf des ObjectOutputStreamKonstruktors der Puffer mittels fl ush () geleert werden. Diese Verhaltenweise wird im Client- und Serverprogramm berücksichtigt, ansonsten "WÜrden sich die Programme gegenseitig blockieren. Zum Kommando II st: Alle Dateinamen (keine Namen von Unterverzeichnissen) des Serververzeichnisses werden in den Ausgabestrom als Arra y vom Typ Fi1e geschrieben. Zum Kommando get: Wird die angeforderte Datei im Serververzeichnis nicht gefunden oder überschreitet die Dateigröße den Maximalwert WlX_S IZE. so wird ein Objekt vom Typ Exception in den Ausgabestrom geschrieben. Ansonsten wird die Datei in ein Byte-Array übertragen und dieses dann in den Ausgabestrom geschrieben. FileClient lmport lmport lmport lmport lmport l mport l mport l mport lmport lmport ja va ja va ja va java java java java java ja va ja va lo. BufferedOutputStream; lO.BufferedReader; lo.ByteArray InputStream; l O.Fl l e ; l o.Fl l eOut put St ream; lo. IO Except lon; lo. InputStreamReader; lO ObjectInputStream; lO.Obj ectOutputStream; net.Sock et; public class Fi le Cl ie nt prlvat e Strlng host; 4.4 Ein Download-Programm prlvate prlvate private private private 133 lnt port; File dir; ObjectInputStream in; ObjectOutputStream out; BufferedReader input; public FileClient(String host. int port. File dir) ( this.host ~ host; this.port ~ port; this.dir ~ dir; public void doWork() { Socket socket ~ null; try ( socket ~ new Socket(host. port); in = new ObjectlnputStream(socket.getlnputStream()); out = new ObjectOutputStream(socket.getOutputStream()); out. fl ushr ) ; input ~ new BufferedReader(new InputStreamReader( System. in) ) ; whi 1e (true) { System.out.println( "Kommando eingeben (lis t get <file> String cmd ~ input.readLine(); if (cmd ~~ null I1 cmd.equals("q")) ( doOuit() ; break; 1 ) i f (cmd. equals ("1ist")) doList(); else if (cmd.startsWith("get ")) ( String file ~ cmd.substring(4).trim(); doGet(fi le); else System. out. pri nt 1n("Ungue1ti ger Befehl"); in.close() ; out.close(); input.close(); catch (Exception e) ( System.err.println(e); finally { try { i f (socket ! ~ null) socket. cl ose () ; 1 q);"); 4 134 Client/Server-Anwendungen mit TCP catch (IOException e) ( private void doOuit() throws IOException ( out. wri teObject ("q"); private void doList() throws IOException, ClassNotFoundException { out, wri teObject ("1i st") ; Fil e[] files - (Fil e[]) in,readObject(); for (File f files) ( System,out,println(f,getName()); private void doGet(String file) throws IOException, ClassNotFoundException ( out.writeObject("get " + file); Object obj - in,readObject(); if (obj instanceof Exception) System,out,println(obj); return; BufferedOutputStream fout - null; try ( File f - new File(dir, file); ByteArraylnputStream bin - new ByteArraylnputStream( (byte[]) obj); fout - new BufferedOutputStream( new FileOutputStream(f)); byte[] b - new byte[1024]; l nt c : while ((c - bin read(b)) !- -1) ( fout,write(b, 0, c) : } catch (IOException e) System,err,println(e); finally { try { if (fout !- null) fout,close(); catch (IOException e) 4.5 Ein Chat-Programm System.out.prlntln(flle + " wurde uebertragen"); public static void main(String[J args) ( if (args.length !- 3) ( System.err .prlntln("java FileClient <hast> <port> <dir>"); System. exi t (1) ; String host - args[OJ; int port - Integer.parselnt(args[lJ); File dir - new File(args[2J); if (!dir.isOirectory()) ( System. err. pri nt ln (args[2] + " ist kei n Verzei chni 5"); System. exi t (1) ; new FileClient(host. port. dir).doWork(); Der Client wird mit den Parametern Rechnername, Portnummer und lokales Verzeichnis aufgerufen. Die übertragenen Dateien werden in dem vorgegebenen Verzeichnis gespeichert. Zu Beginn wird geprüft, ob der dritte Parameter ein gültiges Verzeichnis bezeichnet. über den Ausgabestrom out werden die Kommandos gesendet. über den Eingabestrom in werden Objekte gelesen und entsprechend ihrem Typ verarbeitet: Ausgabe einer Liste von Dateinamen, Speicherung des übertragenen Dateiinhalts, Ausgabe der Fehlermeldungen. 4.5 Ein Chat-Programm Mit dem Chat-Client ist das so genannte "Chatten" mit mehreren Programm 43 Teilnehmern im Netz möglich. Der Teilnehmer kann sich anund abmelden und Textzeilen an alle anderen aktiven Teilnehmer senden. Der Chat-Server registriert die angemeldeten Teilnehmer und verteilt eingehende Nachrichten an alle registrierten Teilnehmer. Der Chat-Client wird sowohl als Applikation mit grafischer Oberfläche als auch als Applet realisiert. 135 4 136 ChatServer lmport lmport lmport lmport import import import import ja va ja va ja va ja va ja va ja va ja va ja va Client/Server-Anwendungen mit TCP lo.BufferedReader; lo. IO Exceptlon; lo. InputStreamReader; lo.PrintWriter; net. InetAddress; net.ServerSocket; net.Socket; util .Vector; public class ChatServer ( private Vector<Pri nt Writer> manager new Vector<PrintWriter>(); priv at e int port ; public ChatServer(int port) ( this port ~ port; public void startServer() { try ( ServerSocket server = new ServerSocket(port) ; Inet Address addr ~ Inet Address getLocal Host(); System.out.println("ChatServer auf" + addr. get HostName() + "/" + addr. getHostAddress() + '";" + port + " gestartet ... "); whi le (true) ( Socket client = ser ver.accept(); new ChatThread(client).start(); } catch ( IOException e) System.err.println(e); privat e class ChatThread extends Thread ( private f i nal static int TIMEO UT ~ 600000; // 10 Min. private Socket client; pri vate String name; pri vate BufferedReader in; pri vate PrintWriter out; public ChatThread(Socket client) ( this client = client; pub lic voi d run() ( String clientAddr ~ client.get lnetAddress() .getHostAddress(); int clientPort ~ client.getPort(); 4.5 Ein ehat-Programm 137 try ( client setSoTi meout(TIMEOUT); new BufferedReader(new Input St reamReader (cl le nt .get lnputStream())); out ~ new PrintWriter(client.getOutputStream(). true); ln = login(); System.out.println("Verbindung zu " + cli entAddr + cII entPort + " aufgebaut: " + narre); Strlng message; wh il e (( message ~ in readLine()) send~ss age (na rre !~ + '"." null) + ". " + rress age) ; in .close() ; out.close(); catch (IOException e) System.err.println(e); finally ( logout() ; try { if (cl ient !~ null) cl ient.close(); ) catch (IOException e) ) System.out.println("Verbindung zu " + cli entAddr + cll entPort + " abgebaut: " + name); pri vate voi d login() throws IOExcepti on ( manager.add (out); name = ln.readLlne(); send ~ssag e(narre + " i st dazugekomrren"); private voi d logout() ( manager.remove(out); sendr-12ssage(narre + " hat s ich verabschledet") ; prlvate vOld sendMessage(Strlng message) synchronized (manager) { for (PrintWriter out manager) ( out.println( message); + '"." 4 138 Client/Server-Anwendungen mit TCP public static voi d main(String[] args) i nt port ~ Int eger . pars el nt (args [ O] ) ; new ChatServer(port).startServer(); Das Programmgerüst entspricht den in den letzten Abschnitten behandelten Beispielen. Kernstück des Programms ist die runMethode. Der Server verwaltet ein Vector-Objekt manager. Mit Hilfe dieses Objekts "merkt" sich der Server, wer sich als Teilnehmer angemeldet hat. Nach Aufnahme der Verbindung wird die Referenz out auf den Ausgabestrom für diesen Teilnehmer im Vektor gespeichert (siehe Methode 1ogi n) und am Ende aus dem Vektor wieder entfernt (siehe Methode logout). Die erste Textzeile, die der Server über tn erhält, ist der LoginName des Teilnehmers. Alle anderen empfangenen Zeilen sind Nachrichten des Teilnehmers an alle aktiven Teilnehmer. Alle Nachrichten werden mit dem Teilnehmernamen versehen und mit der Methode sendMessage an alle angemeldeten Teilnehmer gesendet (siehe Bild 4.5). Mit derselben Methode werden die Teilnehmer darüber informiert, wer sich an- oder abgemeldet hat. Bild4S· ChatServer Der Server als Reflektor / / / ChatClien t 1 ChatClient i , ~ -. -. ChatClient 2 ChatClient 3 Der Chat-Client ist so programmiert, dass er als Applet oder als eigenständige Applikation eingesetzt werden kann. Das Einlesen der Nachrichten vom Server erfolgt in einem eigenen Thread (siehe run-Methode), der in der Methode 1ogi n gestartet wird. 45 Ein ehat-Programm lmport lmport lmport lmport lmport lmport lmport lmport lmport lmport lmport lmport lmport lmport lmport java. awt. IlJ rder Layout ; java. awt Contalner; java. awt. Ev ent Queue ; java. awt. Fl owLayout; java. awt. Font ; java. awt event.Äctlon Event; java. awt event.ActlonLlstener; java. awt event. WindowAdapter; java. awt.e vent. WlndowEvent; java. io BufferedReader; java. io BufferedWri ter; java. to . IOExcept i on; java. to . Input St reamReader ; java. lO.OutputStreamWrlter; java net.Socket; lmport lmport lmport lmport lmport lmport import import javax.swlng javax.swlng javax.swlng javax.swlng javax.swlng javax.swing javax.swing javax.swing JApplet; JButton; JFrame; JLabe 1; JPanel; JScro11 Pane; JTextArea; JTextField; public class ChatClient extends JApplet implements Runnable. ActionListener { pri vate final static int width ~ 500; pri vate final static int hight ~ 400; private pri vate pri vate pri vate JLabel label; JTextArea area; JTextField text; JButton button; private pri vate pri vate pri vate pri vate String hast; int port; Socket socket; BufferedReader in; BufferedWrlter out; private volatile Thread t; prlvate boolean logln = false; prlvate Strlng name; // Wird verwendet. um den Client als Applikation zu starten. prl vate statlc J Frame frame; // Kommandozellenparameter für dle Socket Inl t l al l sl er ung private static String[] cmdLineArgs; 139 4 140 Client/Server-Anwendungen mit TCP 11 De r Client kann auch als Applikation gestartet werden. 11 Dies wird ermöglicht. indem ein Frame für das Applet 11 zur Verfügung gestellt wird. static public void main(String [] args) { if (args.length !- 2) ( System. err. pr int l n( "java ChatCllent <hast> <port>"); System.exit(l); 11 Wird für Socket Initialisierung benötigt. cmdLlneArgs = args; 11 Der Cl ient final ChatClient client - new ChatClient(); frarre - new JFrame("Chat-Client"); fra rre .addWindowListener(new WindowAdapter() ( public void windowClosing(WindowEvent e) ( client.destroy(); System.exit(O); } }) ; client. init(); frarre.getContentPane().add(client); frarre.setSize(width. hight); frarre.setVisible(true); public ChatClient() ( label - new J Label(" "); JPanel top - new JPanel (); top. add (1 abe1) ; area = new JTextArea(); area.setFont(new Font("11mospaced". Font. PLAIN. 14)); area.setLlneWrap(true); area.setEditable(false); text - new JTextField(48); text. setF ont (new Font("11mospaced". Font. PLAI N. 14)); text.setEnabled(false); text.addActionListener(this); button - new JButton("Login"); button.setEnabled(false); button.addActionListener(this); JPanel input - new JPanel (); input.setLayout(new FlowLayout(FlowLayout LEFT. 10. 10)); input.add(text); 4.5 Ein ehat-Programm 141 input.add(button); Contalner c = getContentPane(); e.add(top. BorderLayout.NORTH); e.add(new JSerollPane(area). BorderLayout.CENTER); e.add(input. BorderLayout.SOUTH); // Verbindungsparameter werden bereitgestellt. publie void init() { if (frame ~~ null) ( host ~ getCodeBase().getHost(); port ~ Integer.parselnt(getParameter("port")); else ( host ~ emdLineArgs[O]; port ~ Integer.parselnt(emdLineArgs[I]); text.setEnabled(true); text.requestFoeus(); button.setEnabled(true); // Die gewünschte Benutzer-Aktion wird ausgeführt. publie void aetionPerformed(AetionEvent e) ( Objeet obj ~ e.getSouree(); String emd ~ e.getAetionCommand(); try { i f (obj ~~ button) { i f (emd. equals ("Logi n")) name ~ text.getText(); if (name.length() !~ 0) ( logi n(); } else ( destroy() ; i f (obj ~~ text) { if (login) ( out.write(text.getText()); out. newL ine(); out. fl ush(); } eateh (IOExeeption ex) { area .append (ex. get M2ss age() destroy() ; + "\ n") ; 4 142 Client/Server-Anwendungen mit TCP finally ( text. setText(""); text.requestFocus(); // LOQln beim Server private voi d login() throws IOException ( socket ~ new Socket(host. port); in = new BufferedReader(new Input St reamReader (socket .get lnputStream())); out = new BufferedWriter(new OutputStreamWriter(socket .getOutputStream())); out.wrlte(name) ; out.newLine() ; out. fl ush() ; login = true; 1abel .setText (name) ; button. setText( "Logout" ); t ~ new Thread(this); t.start() ; // Logout beim Server public void destroy() if (login) { try ( login ~ false; 1abel .setText(" "); button. setText( "Logi n") ; t ~ null; if (socket !~ null) socket.close(); if (in !~ null) in.close(); i f (out !~ null) out.close() ; catch (IOException e) public voi d run() { try ( while (Thread.currentThread() ~~ t) final String msg ~ in.readLine(); i f (msg ~~ null) break; 4.6 Klassen über das Netz laden 143 doUpdat e(new Runnable ( ) { public voi d run() { eree.eppendtmsc + "vr"): } }) ; } catch O OExcepti on e ) { pri vate voi d doupdetet gumeöle r ) { try { Eventüeue. i nvo Lel.ater(r) ; catch (Exception e) { '!lJ ehat -Client Bild 4.6: B Chat-Client Hligo Hu g o i ~t dazug ~ k oMl1 ~n Emi l i~ t d a zug ~k oMl1 ~n Hu g o : Hall o , all~ ~ klar ? Emi l : Natürlich' I D a~ 4.6 Pr ogr arm1 funktioni~r~ I[ l ogout [ Klassen über das Netz laden In Java müssen Klassen erst dann physisch vorhanden sein, wenn sie verwendet werden sollen (dynamisches Laden von Klassen). Ein so genannter Klassen/ader ist verantwortlich für das Auffinden und Laden von Klassen. Die Klasse jeva.: mg. Cl assLoader ist eine abstrakte Klasse. Sie etasstoader enthält die Methode loadClass, die die Klasse mit dem angegebenen Namen lädt: pub 1i c Cl ass<?> loadClass (Stri ng nare) throws Cl assNotFound Excepti on 4 144 Client/Server-Anwendungen mit TCP Ein eigener Klassenlader (als Subklasse von Cl assloader) kann z. B. eine Klasse über das Netz laden. Hierzu muss nur die ClassLoader-Methode protected Cl ass<?> findClass(Strlng name) throws ClassNotFoundExceptlon in geeigneter Weise überschrieben werden . Programm 4.4 Im Folgenden entwickeln wir einen Server (ClassServ er), der auf Anfrage eine Klasse in seinem Serververzeichnis sucht und den Inhalt als Byte-Array liefert. Der Client (NetworkCl assloader) überschreibt als Subklasse von ClassLoader die Methode fi ndC l ass. Dazu nutzt er die Cl assLoader-Methode defl neCl ass, um aus dem vom Server gelieferten Byte-Array ein Cl ass-Objekt zu erzeugen, protected flnal Cl ass <?> defineClass( Str ing narre. byt e[] b , int off. int l en) Achtung Beim Einsatz eines Programms mit eigenem Klassenlader ist generell zu beachten, Es wird versucht, weitere in der soeben geladenen Klasse referenzierte Klassen mit dem gleichen Klassenlader zu laden, mit dem auch die erste Klasse geladen "WUrde. Befinden sich das aufgerufene Programm (die Start-Klasse) und die zu ladende Klasse im gleichen Klassenpfad. so wird diese Klasse vom Standard-Klassenlader des Systems und nicht vom eigenen Klassenlader geladen. ClassServer Der Server wird mit Portnummer und Suchpfad aufgerufen. Da der Name der angeforderten Klasse auch den Paketnamen enthalten kann. werden Punkte durch Schrägstriche ersetzt und somit die einzelnen Teile des Paketnamens auf Verzeichnisnamen abgebildet. lmport lmport lmport lmport lmport lmport lmport lmport lmport ja va ja va ja va ja va ja va ja va ja va ja va ja va lO.Buffered Reader; lO. Flle; lo. Flle InputStream; lo. IO Exceptlon; lo. InputStreamReader; lO.ObjectOutputStream; net. InetAddress; net.ServerSocket; net.Socket; 4.6 Klassen über das Netz laden 145 publlC class ClassSer ver extends Thread prlvate lnt port; prlvate Strlng searchPath; public ClassSer ver(int port. String searchPath) ( this.port ~ port; thls.searchPath = searchPath; public voi d startServer() { try ( ServerSocket ser ver = new ServerSocket(port); Inet Address addr ~ Inet Address .get Local Host ( ) ; System.out.println("ClassServer auf" + addr. getHostNarre () + "/" + addr. getHostAddress () + ".;" + port + " gestartet ... "); whi 1e (true) ( Socket cllent = server accept(); new Cl assThread(client. searchPath).start() ; } catch (IOException e) System.err.println (e); pri vate class ClassThread extends Thread ( prlvate Socket ellent; prl vate Strlng searchPath; public ClassThread(Socket cli ent. String searchPath) ( thls.cllent = ellent; thls.searchPath = searchPath; public void run() ( String cli entAddr ~ client.get lnetAddress() .get HostAddress ( ) ; int clientPort ~ client.getPort(); System.out.println("Verbindung zu " + clientAddr + '";" + cl i ent Port + " aufgebaut"); try ( BufferedReader in ~ new BufferedReader( new Input St reamReader (cl i ent .getl nput St ream( )) ) ; ObjectOutputStream out ~ new ObjectOutputStream(client .getOutputStream()) ; out. fl ush(); String name in.readLine(); 4 146 Client/Server-Anwendungen mit TCP name = narre. rep 1ace (' . " '/'); Strl ng cl assPath = searchPath + name + ". cl ass" ; File file ~ new File(classPath); int length ~ (int) file.length(); FilelnputStream fis ~ new FilelnputStream(file); byte data[J ~ new byte[lengthJ; l nt cnt = 0; while (cnt < length) ( int result ~ fis.read(data. cnt , length if (result ~~-1) break; cnt += resu l t : } fis .close(); cnt); if (data !~ null) ( out.writeObject(data); out. fl ush () ; in.close(); out.close() ; System.out.println("\"" + classPath + "\" gesendet"); catch (IOException e) ( System.err.println(e); finally { try { if (client !~ null) cl i ent. cl ose(); catch (IOException e) System.out.println("Verbindung zu " + clientAddr + ".;" + cl i entPort + " abgebaut"); public static void main(String[J args) { if (args.length !~ 2) ( System.err .prlntln("java ClassServer <port> <searchPath>"); System.exit(l); int port ~ Integer.parselnt(args[OJ); String searchPath ~ args[lJ; searchPath ~ searchPath. rep 1ace(' \ \', '/'); 4.6 Klassen über das Netz laden 147 i f (!searchPath.ends With ("j")) searchPath += "I"; new ClassServer(port, searchPath).startServer(); lmport lmport lmport lmport import import java. io EOFException; java. to . IOExcepti cn: java. io ObjectlnputStream; java. io. Pri nt WrHer; java net. ConnectExcepti on; java net.Socket; NetworkClassLoader public class NetworkClassLoader extends ClassLoader ( private String hast; private int port; public NetworkClassLoader(String host. int port) ( this.host ~ host; this.port ~ port; public Cl ass<?> findClass(String name) ( byte[] b ~ 10adClassData(name); return defineClass(name. b. D. b.l engt h) ; pri vate byte[] 10adClassData(String name) { Socket socket ~ null; try ( socket ~ new Socket(host. port); ObjectlnputStream in ~ new ObjectlnputStream(socket .get lnputStream()); PrintWriter out = new PrintWriter(socket .getOutputStream(). true); out.pr intln(name); Object obj ~ in.readObject(); in.close() ; out.close(); return (byte[]) obj; catch (Connect Exception e) { throw new RuntimeExcepti on("Verbi ndung zum Server " "konnte ni cht hergeste11 t werden."); catch (EOFException e) ( t hrow new RuntimeException( "Kl asse wurde ni cht gefunden."); + 4 Client/Server-Anwendungen mit TCP 148 catch (Exception e) ( throw new Runt l me Exception(e); finally { try { if (socket !- null) socket.close(); catch ( IOException e) Statt Das Programm Sta rt wird mit den folgenden Parametern aufgerufen: • Name bzw. IP-Adresse des entfernten Rechners • Portnummer des entfernten Rechners • Name der zu ladenden Klasse, die die ma in-Methode enthält • evtl, Aufrufparameter für die mai n-Methode Das Reflection-APlwird genutzt, um die main-Mefhode der geladenen Klasse mit evtl. Parametern aufzurufen. public class Start ( publ i c static void ma in(String [J args) { if (args.l ength < 3) ( System.err.println( "java Start <hast> <port> <className> [<paramI> ... ]"); System.exit(l); String host - args[OJ ; int port - Int eger .parsel nt (args [l J ) ; String className - args[2J; try ( NetworkClassLoader loader - new NetworkClassLoader(host. port) ; Cl ass<?> c = loader.loadClass(className); // Bereitstellung der main-Methode java.lang.reflect.Method m - c.getMethod("main". new Class[J ( Str ing[J.class j); Str ing[J params - new String[args.length for (int i params[i = 3; i < args.l ength; i ++ ) 3J - args[i J; 3J; 4.6 Klassen über das Netz laden 149 // Aufruf der main-Methode mit evtl. Parametern m.invoke(null. new Objecte] ( params j); catch (Runt imeExcept i on e) ( System.err.println (e.getMessage()); catch (Exception e) ( System.err.println(e); Zur Demonstration des Verfahrens kann z. B. das Programm 4.3 (ChatCli ent) genutzt werden. ClassServer ~~ ChatClient ~~ Bild 4.7' Dynamisches Laden über das Netz I Start I b yt e ] ] I I Rechner A Rechner B 10.108.105.96:40000 Das Laden unbekannter Klassen aus dem Netz stellt ein Sicherheitsrisiko dar. Eine Möglichkeit. sich zu schützen. besteht darin. den Security Manager zu nutzen und nur so viele Zugriffsrechte wie nötig für die Ausführung der Anwendung zuzulassen. Die Zugriffsrechte. passend zum obigen Beispiel. werden in einer Policy-Datei beschrieben: grant { permi ssi on ja va.1 ang.Runti mePermi ssi on "createCl assloader" ; permission ja va.lang.RuntimePermission "exitVM"; permi ssi on ja va. awt .AWTPermi ssi on "shol'Mi ndol'Mi thoutWa rni ngBanner" ; 11 Cl assSer ver permission ja va.net.SocketPermission "10.108.105.96: 40000" , "connect"; 11 ChatServer permission ja va.net.SocketPermission "10. 108.105.96:50000", "connect"; {: Aufruf des Start-Programms (Kommando in einer Zeile} ja va -Djava.security.manage r -Djava.secu rity.policy= policy.txt -cp bin Start 10 .108 .105 .96 40000 ChatCli ent 10.108.105.96 50000 policy.txt 4 150 4.7 Client/Server-Anwendungen mit TCP Remote Procedure Call In diesem Abschnitt entwickeln wir ein allgemeingültiges einfaches Verfahren zum Aufruf von Methoden, die auf einem entfernten Rechner implementiert sind (RPC = Remote Procedure Call). Programm 4.5 Einzige Voraussetzung ist: Die Parameterwerte einer entfernten Methode müssen Objekte sein. Die Klassen aller Objekte (Rückgabewert und Parameterwerte) müssen das Interface ja va.l o. Serl al i zabl e implementieren. Der Client ruft die gewünschte Methode mit Hilfe von Object call (String narre. Objecte] params) der Klasse RPCCl i ent auf. narre ist der Methodenname, params enthält die Parameterwerte. Beispiel, Lautet die Deklaration der entfernten Methode public int getSumrre(lnteger x , Integer s>. so sieht der Codeabschnitt des Clients für den Aufruf beispielsweise so aus: Objecte] params ~ (ID. 33); i nt sumrre ~ ( Integer) rpcCl i ent ca11 ("getSumme". params); RPCClient Die Klasse RPCCl i ent nutzt die Methoden writeObject und readObject der Klassen ObjectOutputStream bzw. ObjecUnputStream. um den Methodennamen, das Parameter-Array und den Rückgabewert über das Netz zu transferieren. Bei einem Rückgabewert vom Typ Exceptlon wird eine Ausnahme dieses Typs ausgelöst. l mport l mport l mport l mport java java java java i c. IOException; i 0 ObjectInputStream; iO.ObjectOutputStream; net.Socket; public class RPCClient prlvate Strlng host; prlvate lnt port; public RPCClient(String host. int port) { th is.host ~ host; th is port ~ port; 4.7 Remote Procedure Call pub1i c Object ca11 (Stri ng narre. Objecte] params) throws Exception { Socket socket ~ null; try ( socket ~ new Socket(host. port); ObjectOutputStream out ~ new ObjectOutputStream(socket .getOutputStream()) ; out writeObject(narre); out writeObject(params); out. flush(); ObjectlnputStream in ~ new ObjectlnputStream(socket .getlnputStream()); Object ret ~ in.readObject(); in.close() ; out.close(); lf (ret lnstanceof Exceptlon) throw (Exception) ret; return ret; fina11y { try { i f (socket ! ~ null) socket. cl ose () ; catch (IOException e) Die Klasse RPCServer nutzt das Reflection-Al'I. Sie lädt diejenige RPCSeroer Klasse (hier als Service bezeichnet). die die Implementierung der entfernten Methode enthält, und erzeugt eine Instanz dieser Klasse. Dazu muss letztere Klasse den parameterlosen Konstruktor zur Verfügung stellen. Anhand des Methodennamens und der Parameterwerte, die vom Client als Objekte übermittelt wurden. wird mit Hilfe der ClassMethode getMethod die gewünschte Methode des Service als Method-Objekt zur Verfügung gestellt. Anschließend wird mit i nvoke die Service-Methode aufgerufen. Wenn die Service-Methode eine Ausnahme auslöst, so löst l nvoke die Ausnahme java lang.reflect.InvocatlonTargetExceptlon aus. 151 4 152 Client/Server-Anwendungen mit TCP Die Invocatl onTargetExceptl on-Methode Throwable getTargetException() gibt die von der aufgerufenen Service-Methode ausgelöste Ausnahme ZUlÜCk. Der RPCServer liefert Ausnahmen (Typ Exception), die beim Suchen bzw, bei der Ausführung der Service-Methode auftreten können, als "normales" Ergebnisobjekt des Aufrufs zurück. l mport l mpor t l mpor t l mpor t l mpor t l mpor t l mport l mport java java java java java java java java i c. IOException; i 0 ObjectlnputStream; io.ObjectOutputStream; lang.reflect.InvocatlonTargetExceptlon; lang.reflect.Method; net.lnetAddress; net.ServerSocket; net. Socket; public class RPCServer extends Thread ( prlvate int port; private String service; public RPCServer(int port. String service) ( this port ~ port; this service = service; public void startServer() throws Exception ( ServerSocket server = new ServerSocket(port); InetAddress addr ~ InetAddress .getLocalHost(); System. out. printl n( "RPCServer auf" + addr. getHostName() + "/" + addr. getHostAddress() + ":" + port + " gestartet ... "); System.out.println("Service: " + service); Class<?> servlceClass = Class.forName(service); Object serviceObject = servlceClass.newInstance(); whi le (true) ( Socket cllent = server.accept(); new RPCThread(client. serviceObject).start(); private class RPCThread extends Thread { prlvate Socket cllent; prlvate Object servlceObject; 4.7 Remote Procedure Call public RPCThread(Socket client. Object serviceObject) thls.cllent = client; this.serviceObject = serviceObject; public void run() { try ( ObjectlnputStream in ~ new ObjectlnputStream(client .getlnputStream()); String name ~ (String) in.readObject(); Object[] params ~ (Object[]) in.readObject(); Class<?>[] types ~ new Class[params.length] : for (int i = 0; i < params.length; i++) types[i] ~ params[i].getClass(); Object ret ~ null; try ( Method m ~ serviceObject.getClass().getMethod(name. types) ; ret = m.invoke(serviceObject, params); } // Eine Ausnahme wird als Ergebnisobjekt / / zurückgel i efert catch (InvocationTargetException e) ret ~ e.getTargetException(); catch (Exception e) ( ret = e; ObjectOutputStream out ~ new ObjectOutputStream(client .getOutputStream()) ; out.writeObject(ret); out. fl ush(); in.close() ; out.close(); catch (Exception e) ( System.err.println(e); finally { try { if (cl ient !~ null) cl ient.close(); catch (IOException e) 153 4 154 Client/Server-Anwendungen mit TCP public static void main(String[] args) throws Exception { if (args.length !- 2) ( System.err.prlntln("java RPCServer <port> <service>"); System.exit(l); int port - Integer.parseInt(args[D]); String service = args[l]; new RPCServer(port, servlce).startServer(); Test Einige beispielhafte Service-Methoden sind in der Klasse DemoService implementiert. Test.Cl ient enthält den Aufruf dieser Methoden. DemoService import import import import import java java java java java iO.BufferedReader; iO.FileReader; io.IOException; util.Date; util .Vector; pub 1i c cl ass I:€ITDSer vi ce ( public String getEcho(String text) ( return text; public int getSumrre(lnteger x , Integer y) ( return x + y; pub 1i c Date getOate () return new Date(); public void sendMessage(String msg) System.out.println(msg); public Vector<String> getMessages(String file) throws IDException ( Vector<String> lines = new Vector<String>(); BufferedReader in - new BufferedReader( new FileReader(file)); String line; 4.7 Remote Procedure Call 155 while ( (l i ne - in.read Line()) !- null) ( 1i nes. add(l i ne); ) in.close() ; return 1i nes; import java.utll.Date; import java.utll.Vector; TestClient public class TestClient ( public static void main(String[J args) throws Exception ( String host - args[OJ; int port - Int eger .parseI nt (args [I J ) ; RPCClient rpcClien t - new RPCClien t(host. port); Object[J params l - ( "Hallo" l: String s - (St ri ng) rpcClient.call ("get Echo" . paramsl); System. out. pr i nt 1n("getEcho; + s); Object[J params2 - ( 10. 33 ); int summe - (Integer) rpcClient call("getSumrre". params2); System. out. pr i nt 1n("getSumrre; + surrrne); Object[J params3 - (); Date date - (Date) rpcClient ca11 ("getOate". params3); System. out. pr i nt 1n("getOate; + date); Object[J params4 - ( "Dies ist ein Test." ); rpcClient.call ("sendl'essage". params4); Object[J params5 - ( "rnsg. txt" ); @SuppressWarnings("unchecked") Vector<String> v = (Vector<String» rpcClient.call ( "geU1essages", params5); System. out. pr i nt 1n("getHessages; ") ; for (String msg v) ( System.out.println(msg); Aufruf des Servers: start ja va -cp bin RPCServer 50000 DemoSer vice Aufruf des Client: java -cp bin TestClient localhost 50000 4 156 4.8 Client/Server-Anwendungen mit TCP Thread-Pooling Die Fähigkeit des Servers, mehrere Clients quasi gleichzeitig zu bedienen, wurde bisher so gelöst, dass für jede Anfrage eines Clients ein neuer Thread erzeugt "WUrde. Im Vergleich zu Prozessen ist die Erzeugung eines Threads weniger aufwändig. Trotzdem sollte man hiermit sparsam umgehen; insbesondere dann, wenn kurzzeitig sehr viele Threads mit kurzer Laufzeit benötigt werden. Tbread-Pool Ab der Version Java SE 5 gibt es die Möglichkeit, einen ThreadPool einzusetzen. Dieser bietet die Möglichkeit, mehrere separate Aufgaben vom selben Thread nacheinander ausführen zu lassen. Nicht mehr benutzte Threads werden in den Pool zurückgelegt und können wiederverwendet werden. Wir benutzen hier eine spezielle Thread-Pool-Variante, einen so genannten Cached Thread Pool, Ein solcher Pool wächst bzw, schrumpft nach Bedarf Steht eine neue Aufgabe (hier eine Client-Anfrage) zur Bearbeitung an und gibt es keinen "freien" Thread im Pool, so wird ein neuer erzeugt, der die Ausführung übernimmt Ist die Aufgabe ausgeführt, steht dieser Thread als wieder "freier" Thread im Pool zur Verfügung. Wird er innerhalb von 60 Sekunden nicht benötigt, so wird er terminiert und ist dann nicht mehr verwendbar, Der Pool passt sich also dynamisch den momentanen Anforderungen an und ist optimal für kleinere Aufgaben, die in hoher Zahl kutzfristig anstehen, Für unsere Zwecke benutzen wir die Klasse ja va. uti l . concurrent. Execut or s und das Interface java. uti l .concurrent. Execut orServ l ce . Die statische Execut ors-Metho de statlc Execut or SerV l ce newCachedThreadPool( ) erzeugt einen Cached Thread Pool und liefert diesen als Objekt vom Typ Execut orSerVlce zurück, Das Interface Execut or Ser vl ce enthält u. a. die folgenden Methoden, voi d execute(Runnable taskl führt die r un-Methode von task in einem Thread des Pools aus. voi d shutdown () bewirkt, dass vor dem Aufruf dieser Methode übergebene Aufgaben noch ausgeführt, neue aber nicht mehr akzeptiert werden; alle vom Pool verwalteten Threads werden dann terminiert. Damit ein Programm ordnungsgemäß beendet werden kann, muss der Pool mit der Methode shutdo wn kontrolliert terminiert werden. 4.8 Thread-Pooling 157 Programm 4.6 demonstriert den Einsatz eines Thread-Pools für Programm 4.6 einen Server. lmport lmport l mport lmport lmport lmport lmport java.lo BufferedReader; java.lo.IOExceptlon; j ava. jo . InputSt reamReader ; java.lo.PrlntWrlter; java net.ServerSocket; java net.Socket; java text.SimpleDateFormat; import java.utll.Date; import java.util.concurrent ExecutorService; import java.util.concurrent Executors; publie elass Server ( private int port; private ExecutorService pool; publie Server(int port) this.port ~ port; publie void startServer() { try ( ServerSocket server = new ServerSocket(port); System.out.println("Server gestartet ... "); pool = Executors.newCachedThreadPool(); whi 1e (true) ( Socket client = server.accept(); pool .execute(new Handler(client)); } eateh (IOExeeption e) System.err.println(e); pool.shutdown() ; private elass Handler implements Runnable ( private Socket client; publie Handler(Soeket elient) this.client = client; publie void run() { try ( BufferedReader in ~ new BufferedReader( new InputStreamReader(elient.getlnputStream())); Server 4 158 Client/Server-Anwendungen mit TCP PrintWriter out = new PrintWriter(client .getOutputStream(). true); String input ~ in.readLine(); if (input !~null) ( int delay ~ 5000 + (int) (Math.random() long begin ~ 0; long end ~ 0; try ( begin ~ System.currentTimeMillis(); Thread.sleep(delay); end ~ System.currentTimeMillis(); } catch ( InterruptedException e) ( ) Simpl eDateFormat f ~ * 5000); new Simpl eOateFormat( "Hii. nmss") ; out println("Auftrag " + input + ". Beginn: + f.forma t(new Oate(begin)) + ". Ende ; + f.forma t(new Oate(end))); in.close(); out.close() ; catch ( IOException e) System.err.println(e); finally ( try ( if (client !~ null) cl i ent. cl ose(); catch (IOException e) public static voi d main(String[] args) int port ~ Int eger .parsel nt (args [ O] ) ; new Server(port).startServer(); 4.9 Ein Framework für TCP-Server Die Beispiele der vorhergehenden Abschnitte zeigen, dass die Implementierungen der TCP-Server im Großen und Ganzen fast immer dem gleichen Muster folgen. Es liegt also nahe, die immer wiederkehrenden Codeteile zu standardisieren und als Framework für eigene Server-Implementierungen anzubieten. 4.9 Ein Framework für TCP-Server 159 Das Framework besteht aus den beiden Klassen TCPServer und Programm 4.7 AbstractHandler. die zum Paket tcpfrarrework gehören. Der Konstruktor von TCPServer erwartet eine Portnummer und die Klasse des Handlers, der die eigentliche Kommunikation mit dem Client durchführt. Hier wird das Class-Objekt des Handlers angegeben. Der Handler ist eine Subklasse von AbstractHandler. Innerhalb des Konstruktors werden ein ServerSocket-Objekt und ein Thread-Pool (siehe Kapitel 4.8) erzeugt. TCPServer ist von Thread abgeleitet. Innerhalb der run-Methode wird in einer Schleife die Methode accept aufgerufen. Mit dem zurückgegebenen Socket-Objekt wird die handle-Methode aufgerufen. Diese erzeugt eine neue Instanz des Handlers und ruft für diese Instanz die Methode handle mit den Referenzen für das Socket-Objekt und den Thread-Pool auf. Die Methode stopServer schließt das ServerSocket-Objekt und terminiert den Thread-Pool. package tcpframework; lmport lmport lmport lmport l mport l mport java.lo.IOExceptlon; java net.ServerSocket; java net.Socket; java net.SocketExceptlon; j ava. ut II .concurrent. ExecutorSerVl ce; j ava. ut II .concurrent. Executors ; public class TCPServer extends Thread prlvate Cl ass<?> handlerClass; prlvate ServerSocket serverSocket; prlvate ExecutorSerVlce pool; public TCPServer(int port. Class<?> handlerClass) throws IOException ( this.handlerClass ~ handlerClass; serverSocket = new ServerSocket(port); pool ~ Executors.newCachedThreadPool (); public void run() { try { whi 1e (true) ( // Belm Aufruf von stopServer() wlrd elne // SocketException ausgelöst Socket cllentSocket = serverSocket accept(); handle(clientSocket); } catch (SocketException e) { TCPServer 4 160 Client/Server-Anwendungen mit TCP catch (Exception e) ( System.err.println(e); public voi d stopServer() try ( serverSocket.close(); } catch ( IOException e) } poo 1.shutdown () ; pri vate void handle(Socket cli entSocket) throws Exception ( AbstractHandler handler ~ (AbstractHandler) handlerClass . newInstance(); handl er. handl e(c 1i entSocket. pool); Ein konkreter Handler ist von AbstractHandler abgeleitet und implementiert die run-Methode. Diese wird durch die handleMethode in einem Thread des Pools ausgeführt. Zudem wird das Socket-Objekt mit Hilfe der Methode getCl i entSocket zur Verfügung gestellt. AbstractHandler package tcpframework; lmport java net.Socke t; lmport java utll .concurrent ExecutorSerVlce; public abstract class AbstractHandler implements Runnable ( prlvate Socket cllentSocket; public Socket getClientSocket() return cllentSocket; public void handle(Socket cl i entSocket. ExecutorService pool) ( this.clientSocket ~ cl i entSocket; pool.execute(this); public abstract void run(); 4.9 Ein Framework für TCP-Server 161 Das Framework wird nun in einem Beispiel verwendet. Hierbei Anwendung des handelt es sich um den Echo-Server aus Kapitel 4.3. Frameworks lmport java.lo. IOExceptlon; EchoServer lmport tcpframework .TCPServer; public class EchoSer ver ( public static void main(String[] args) throws IOExcept i on ( int port ~ Int eger .parsel nt( args [O] ) ; TCPServer ser ver = new TCPServer (port , EchoH andl er class); server .start(); System.out.println("Server gestartet ... "); // blockiert. bis RE TURN eingegeben wurde System. in. read () ; server.stopServer(); System.out.print l n("Server wird gestoppt ... "); Der Server wird nach Betätigung der Return-Taste gestoppt; allerdings erst dann, wenn alle momentan aktiven ClientBearbeitungen beendet sind. lmport lmport l mport lmport lmport java.lo BufferedReader; java.lo. IOExceptlon; j ava. jo . InputSt reamReader ; java.lo. PrlntWrlter; java net.Sock et; EchoHandler import tcpframework.Abstract Handl er; public class EchoHandl er extends AbstractHandler public void run() ( Socket clientSocket ~ getCli entSocket(); String clientAddr ~ clientSocket.get lnetAddress () .get HostAddress(); int clientPort ~ clientSocket.getPort(); System.out.print ln("Verb indung zu " + clientAddr + '";" + c II entPort + " aufgebaut"); try ( BufferedReader in ~ new BufferedReader(new Input St reamReader (cl ie nt Socket .getlnput St ream( ) ) ) ; 4 162 PrlntWrlter out = Client/Server-Anwendungen mit TCP new PrlntWrlter(cllentSocket .getOutputStream(). true); out prlntln("Server ist berelt ... "); String input; while ((input ~ in.readLine()) out. pri nt 1n(i nput); !~ null) { in.close(); out.close() ; catch ( IOException e) System.err.println(e); finally { try { if (cl ient Socket !~ null) clientSocket.clos e(); catch ( IO Exception e ) ( System.out.println("Verbindung zu " + clientAddr + ".;" + cl i entPort + " abgebaut"); Um mit dem Client zu kommunizieren, wird in der run-Methode das Socket-Objekt über die Methode getel i entSocket erfragt, Dann erfolgt das Empfangen und Senden der Daten. 4.10 1. Aufgaben Entwickeln Sie einen TCP-SelVer, der als Reaktion auf die Verbindungsaufnahme durch den Client an diesen einen in einer Datei gespeicherten Text sendet und dann von sich aus die Verbindung beendet. Testen Sie den Server mit Hilfe von Telnet: telnet localhost 50000 2. Entwickeln Sie einen TCP-SelVer, der die aktuelle Systemzeit des Servers als Zeichenkette liefert. Programmieren Sie auch einen passenden Client hierzu. 3. Entwickeln Sie einen Echo-Server, der die parallele Bedienung mehrerer Clients ermöglicht, aber die Anzahl der 4.10 Aufga ben 163 gle ich zeitig ab..rlve n Threacls auf eine vorgegebene Zahl b esch rä nkt. 4. Entwicke ln Sie für de n File-Se rver aus Kap itel 4.4 eine n Client mit g rafische r Oberfläche. '" """"" ,021_................ __ __ ~ 10 3_1UJpl)1..... ;f U _~ . 1llS_1UJpl)1&d 06)1.).1..... ;1l 1_~ ~ ~'" Bild 4.8 : Rk-Cli e71l ~ ) 1-11 - 1 I 5. I Aus eine r Büch er-Dare nba nk so Uen zu e ine r vo rgegebe nen Buch nu mme r Anga be n zu m Buch (Auto r und Ti tel) über SQL abge fragt we rden. Nutzen Sie das RPC-Verfuh ren a us Kapitel 4.7 und entwickeln Sie hierzu eine n Service mit der Methode publi c Buch getBuch(1nteger 'i d) thrtws Except t on und eine n Client , de r diese entfernte Methode a ufruft . Die Klasse Buch soll die Angabe n zum Buch als Attribute mit den entsp reche nden set- und get-Merhoden enthalten . 6. Angelehnt a n das Beispiel des Kapitel s 2.8 soll ein Server auf Anfrage (Lade n) eine CachedROtJSet -Insta nz an den Client üb ertragen. Diese r soll die Daten in Form einer Tabelle an zeigen (Bild 4.9). Der Besta nd zu einem Buchtitel kann ge än de rt we rde n. Mit "Speichern" soll die Cach edRCl.\ISe t -Instanz zum Server ge schi ckt und die Änderungen in der Datenbank e ingespielt we rden. 164 Md 4.9: Client 4 Client/Server-Anwendungen mit TCP Clie nt ___I S.~ tJ 3-15-001308-9 3-15-001562-6 3-15-010606-0 3-257 -20998-3 3-257-21034-5 3-257-21166-X 3-257-21405- 7 3-257-21406-5 3-351 -030 44-( H58-32655-3 H58-32733-9 H58-32810-6 H58-33004-6 H9 1-96007-X 3-538 -05349-9 3-538 -06656-6 3-538 -06657-( 3-538 -06658-2 :lo.5..3.&OJi9..820 St. nd 23 200 9-07-1 4 18:03:0 102009-07-14 17:50 :3 122009-07-101 UH 132009-07-08 17:48:3 552009-07-0800:00 :0 82009-07-0800:00 :0 332009-07-0800:00 :0 82009-07-0800:00 :0 32 2009 -07-08 1 7 : ~8: 3 302009-07-0800:00 :0 152009-07-0800:00 :0 162009-07-0800:00 :0 202009-07-0800:00 :0 102009-07-0800:00 :0 122009-07-0800:00 :0 22 2009 -07-08 00:00 :0 102009-07-0800:00 :0 152009-07-0800:00 :0 Titel Schwe re lerten Groß e Erw. rtunQen Der W eihnachts abe Niko las Nick leb y David Copp erfield Ble. kh.u s Die Pickw ick ier M. rtin Chuzzlew it W eihnachten m it Di Harte Ze iten Gesc hichte aus LW Ble. k House Nikol. us Nickleby W eihnachts erzählu David Copp erfield Die Pickw ick ier Martin Chuzzlew it Nicho la s Nick leby ctlQJatlIkkla I Laden o..9o.OLQ.8.o.Q:O 11 Speichern