Die Socket-Programmierschnittstelle Client-Server mit Sockets (Prinzip) Server - Zu TCP (bzw. UDP) gibt es keine festgelegten “APIs” - Bei UNIX sind dafür “Sockets” als Zugangspunkte zum Transportsystem entstanden - diese definieren mit einer Programmiersprache dann eine Art “API” - Semantik eines Sockets: analog zu Datei-Ein/Ausgabe - ein Socket kann aber auch mit mehreren Prozessen verbunden sein - Programmiersprachliche Einbindung (C, Java etc.) - Sockets werden wie Variablen behandelt (können Namen bekommen) - Beispiel in C (Erzeugen eines Sockets): int s; s = socket(int PF_INET, int SOCK_STREAM, 0); “Family”: Internet oder nur lokale Domäne “Type”: Angabe, ob TCP verwendet (“stream”) oder UDP (“datagram”) - wird innerhalb der Filedeskriptor-Tabelle des Prozesses angelegt - Datenstruktur wird allerdings erst mit einem nachfolgenden “bind”Aufruf mit Werten gefüllt (binden der Adressinformation aus HostAdresse und einer “bekannten” lokalen Portnummer an den Socket) Family Type socket(); bind(); listen(); accept(); Warteschlange f. Client-Connects Client socket(); connect(); Abbruch bei close des Servers oder Clients read(); write(); ... close(); write(); read(); ... close(); Zeit - Voraussetzung: Client kennt die IP-Adresse des Servers sowie die Portnummer (des Dienstes) - muss beim connect angegeben werden - Mit “listen” richtet der Server eine Warteschlange für Client-connect-Anforderungen ein - Auszug aus der Beschreibung: “If a connection request arrives with the queue full, tcp will retry the connection. If the backlog is not cleared by the time the tcp times out, the connect will fail” - Bibliotheksfunktion “socket” erzeugt einen Deskriptor Datenstrukturen von Dateien Server wird blockiert Portnummer an Socket binden Socketdeskriptor - Accept / connect implementieren ein “Rendezvous” - mittels des 3-fach-Handshake von TCP - bei “connect” muss der Server bereits listen / accept ausgeführt haben - Rückgabewerte von write bzw. read: Anzahl der tatsächlich gesendeten / empfangenen Bytes Local IP Remote IP ... - Varianten: Es gibt ein select, ein nicht-blockierendes accept etc., vgl. dazu die (Online-)Literatur Vert. Sys., F. Ma. 128 Vert. Sys., F. Ma. 129 Client/Server mit Sockets in C Socket-Beispiel: Client (auf den nächsten 4 Seiten) /* create socket */ sock = socket(AF_INET,SOCK_STREAM,0); if(sock < 0) { perror(“open stream socket”); exit(1); } server.sin_family = AF_INET; /* get IP address of host spec. by command line */ hp = gethostbyname(argv[1]); if(hp == NULL) { fprintf(stderr,”%s unknown host.\n”,argv[1]); exit(2); } /* copies the IP address to server address */ bcopy(hp->h_addr, &server.sin_addr, hp->h_length); /* set port */ server.sin_port = PORT; /* open connection */ if(connect(sock,&server,sizeof(struct sockaddr_in)) <0) { perror(“connecting stream socket”); exit(1); } /* read input from stdin */ while(run=read(0,buf,BUF_SIZE)) { if(run<0) { perror(“error reading from stdin”); exit(1); } /* write buffer to stream socket */ if(write(sock,buf,run) < 0) { perror(“error writing stream socket”); exit(1); } } close(sock); - Verwendung von Sockets in C erfordert u.a. - Header-Dateien mit C-Datenstrukturen, Konstanten etc. - Programmcode zum Anlegen, Füllen etc. von Strukturen - Fehlerabfragen und Fehlerbehandlung - Socket-Programmierung ist ziemlich “low level” - etwas umständlich, fehleranfällig bei der Programmierung - aber “dicht am Netz” und dadurch manchmal von Vorteil - Zunächst der Quellcode für den Client: #include #include #include #include #include <sys/types.h> <sys/socket.h> <netinet/in.h> <netdb.h> <stdio.h> #define PORT 4711 #define BUF_SIZE 1024 main(argc,argv) int argc; char *argv[]; { int sock, run; char buf[BUF_SIZE]; struct sockaddr_in server; struct hostent *hp; if(argc != 2) { fprintf(stderr,”usage: client <hostname>\n”); exit(2); } } Vert. Sys., F. Ma. 130 Vert. Sys., F. Ma. 131 Socket-Beispiel: Server #include #include #include #include #include Socket-Beispiel: Server (2) <sys/types.h> <sys/socket.h> <netinet/in.h> <netdb.h> <stdio.h> #define PORT 4711 #define MAX_QUEUE 1 #define BUF_SIZE 1024 listen(sock_1,MAX_QUEUE); /* start accepting connection */ sock_2 = accept(sock_1,0,0); if(sock_2 < 0) Socket für Verbindungswünsche { perror(“accept”); exit(1); Socket für Kommunikation } /* read from sock_2 */ while (rec_value=read(sock_2,buf,BUF_SIZE)) { if(rec_value<0) { perror(“reading stream message“); exit(1); } else /* write received data to stdout */ write(1,buf,rec_value); } printf(“Ending connection.\n“); close(sock_1); close(sock_2); /* random port number */ main() { int sock_1,sock_2; /* file descriptors for sockets */ int rec_value, length; char buf[BUF_SIZE]; struct sockaddr_in server; /* create stream socket in internet domain*/ sock_1 = socket(AF_INET,SOCK_STREAM,0); if (sock_1 < 0) { perror(“open stream socket”); exit(1); } /* build address in internet domain */ server.sin_family = AF_INET; /* everyone is allowed to connet to server */ server.sin_addr.s_addr = INADDR_ANY; server.sin_port = PORT; /* bind socket */ if(bind(sock_1,&server,sizeof(struct sockaddr_in))) { perror(“bind socket to server_addr”); exit(1); } Vert. Sys., F. Ma. } - Sinnvolle praktische Übungen (evtl. auch in Java): 1) Beispiel genau studieren; Semantik der Socket-Operationen etc. nachlesen: Bücher oder Online-Dokumentation (z.B. von UNIX) 2) Varianten und andere Beispiele implementieren, z.B.: - Server, der mehrere Clients gleichzeitig bedienen kann - Server, der zwei Zahlen addiert und Ergebnis zurücksendet - Produzent / Konsument mit dazwischenliegendem Pufferprozess (unter Vermeidung von Blockaden bei vollem Puffer) - Messung des Durchsatzes im LAN; Nachrichtenlängen in mehreren Experimenten jeweils verdoppeln 132 Vert. Sys., F. Ma. 133 Übungsbeispiel: Sockets mit Java Client-Server mit Sockets in Java (auf den nächsten 9 Seiten) - Beispiel aus dem Buch Java Distributed Computing von Jim Farley (O’Reilly) - Auch unter Java lassen sich Sockets verwenden - bequemer als unter C - Paket java.net.* enthält u.a. die Klasse “Socket” - Streamsockets (verbindungsorientiert) bzw. Datagrammsockets - Hier der Client: - Beispiel: import java.lang.*; import java.net.*; import java.io.*; Herstellen einer Verbindung DataInputStream in; PrintStream out; Echo-Port Socket server; Hostname ... server = new Socket(getCodeBase().getHost(),7); // Klasse Socket besitzt Methoden // getInputStream bzw. getOutputStream, hier // Konversion zu DataInputStream / PrintStream: in = new DataInputStream(server.getInputStream()); out = new PrintStream(server.getOutputStream()); ... // Etwas an den Echo-Server senden: out.println(...) Port Nummer 7 sendet alles zurück ... // Vom Echo-Server empfangen; vielleicht // am besten in einem anderen Thread: String line; while((line = in.readLine()) != null) // line ausgeben ... server.close; public class SimpleClient { // Our socket connection to the server protected Socket serverConn; public SimpleClient(String host, int port) throws IllegalArgumentException { Konstruktor try { System.out.println(“Trying to connect to “ + host + “ “ + port); serverConn = new Socket(host, port); } catch (UnknownHostException e) { throw new IllegalArgumentException (“Bad host name given.“); } catch (IOException e) { System.out.println(“SimpleClient: “ + e); System.exit(1); } - Zusätzlich: Fehlerbedingungen mit Exceptions behandeln (“try”; “catch”) - z.B. “UnknownHostException” beim Gründen eines Socket System.out.println(“Made server connection.“); } Vert. Sys., F. Ma. 134 Vert. Sys., F. Ma. 135 public static void main(String argv[]) { if (argv.length < 2) { System.out.println (“Usage: java \ SimpleClient <host> <port>“); System.exit(1); } Der Server Host- und Portnummer von der Kommandozeile import java.net.*; import java.io.*; import java.lang.*; Default-Port, an dem der Server public class SimpleServer { auf eine Client-Verbindung wartet protected int portNo = 3000; protected ServerSocket clientConnect; int port = 3000; String host = argv[0]; try { port = Integer.parseInt(argv[1]); } catch (NumberFormatException e) {} SimpleClient client = client.sendCommands(); Socket, der Verbindungswünsche entgegennimmt public SimpleServer(int port) throws IllegalArgumentException { if (port <= 0) Konstruktor throw new IllegalArgumentException( “Bad port number given to SimpleServer constructor.“); new SimpleClient(host, port); } // Try making a ServerSocket to the given port System.out.println(“Connecting server socket to port“); try { clientConnect = new ServerSocket(port); } catch (IOException e) { System.out.println(“Failed to connect to port “ + port); System.exit(1); } public void sendCommands() { try { DataOutputStream dout = new DataOutputStream(serverConn.getOutputStream()); DataInputStream din = new DataInputStream(serverConn.getInputStream()); // Send a GET command... dout.writeChars(“GET goodies “); // ...and receive the results String result = din.readLine(); System.out.println(“Server says: \““ + result + “\““); } catch (IOException e) { System.out.println(“Communication SimpleClient: “ + e); System.exit(1); Wird vom Garbage-Collector aufgerufen, wenn } } keine Referenzen auf den Client mehr existieren (‘close’ evtl. am Ende von ‘sendCommands’) public synchronized void finalize() { System.out.println(“Closing down SimpleClient...“); try { serverConn.close(); } catch (IOException e) { System.out.println(“Close SimpleClient: “ + e); System.exit(1); } } // Made the connection, so set the local port number this.portNo = port; } public static void main(String argv[]) { int port = 3000; Portnummer von if (argv.length > 0) { int tmp = port; Kommandozeile try { tmp = Integer.parseInt(argv[0]); } catch (NumberFormatException e) {} port = tmp; } SimpleServer server = new SimpleServer(port); System.out.println(“SimpleServer running on port “ + port + “...“); Aufruf der Methode server.listen(); } } Vert. Sys., F. Ma. 136 “listen” (siehe unten) Vert. Sys., F. Ma. 137 public void listen() { try { System.out.println(“Waiting for clients...“); while (true) { Socket clientReq = clientConnect.accept(); System.out.println(“Got a client...“); serviceClient(clientReq); Warten auf connect eines Client, } dann Gründen eines Sockets } catch (IOException e) { System.out.println(“IO exception while listening.“); System.exit(1); } } public void serviceClient(Socket clientConn) { SimpleCmdInputStream inStream = null; Von DataInputDataOutputStream outStream = null; try { Stream abgeinStream = new SimpleCmdInputStream leitete Klasse (clientConn.getInputStream()); outStream = new DataOutputStream (clientConn.getOutputStream()); } catch (IOException e) { System.out.println(“SimpleServer: I/O error.“); } SimpleCmd cmd = null; Klasse SimpleCmd hier nicht gezeigt System.out.println(“Attempting to read commands...“); while (cmd == null || !(cmd instanceOf DoneCmd)) { try { cmd = inStream.readCommand(); } catch (IOException e) { System.out.println(“SimpleServer (read): “ + e); System.exit(1); } Schleife zur Entgegennahme und Ausführung von Kommandos if (cmd != null) { String result = cmd.Do(); try { outStream.writeBytes(result); } catch (IOException e) { System.out.println(“SimpleServer (write): “ + e); System.exit(1); } } } } finalize-Methode hier nicht gezeigt Java als “Internet-Programmiersprache” - Java hat eine Reihe von Konzepten, die die Realisierung verteilter Anwendungen erleichtern, z.B.: - Socket-Bibliothek zusammen mit Input- / Output-Streams - Remote Method Invocation (RMI): Entfernter Methodenaufruf mit Transport (und dabei Serialisierung) auch komplexer Objekte - eingebautes Thread-Konzept - java.security-Paket - plattformunabhängiger Bytecode mit Klassenlader (Java-Klassen können über das Netz transportiert und geladen werden; Bsp.: Applets) Damit z.B. Realisierung eines “Meta-Protokolls”: Über einen Socket vom Server eine Klasse laden (und Objekt-Instanz gründen), die dann (auf Client-Seite) ein spezifisches Protokoll realisiert. (Stichworte: “mobiler Code” bzw. Jini) - Das UDP-Protokoll kann mit “Datagram-Sockets” verwendet werden, z.B. so: try { DatagramSocket s = new DatagramSocket(); byte[] data = {’H’,’e’,’l’,’l’,’o’}; InetAddress addr = InetAddress.getByName(“my.host.com“); DatagramPacket p = new DatagramPacket(data, data.length, addr, 5000); s.send(p); Port-Nummer } catch (Exception e) { System.out.println(“Exception using datagrams:“); e.printStackTrace(); } - entsprechend zu “send” gibt es ein “receive” - InetAddress-Klasse repräsentiert IP-Adressen - diese hat u.a. Methoden “getByName” (klassenbezogene Methode) und “getAddress” (instanzbezogene Methode) - UDP ist verbindungslos und nicht zuverlässig (aber effizient) } Vert. Sys., F. Ma. 138 Vert. Sys., F. Ma. 139 Beispiel: Ein Bookmark-Checker URL-Verbindungen in Java - Java bietet einfache Möglichkeiten, auf “Ressourcen” (z.B. Dateien) im Internet mit dem HTTP-Protokoll lesend und schreibend zuzugreifen import java.io.*; import java.net.*; import java.util.Date; import java.text.DateFormat; public class CheckBookmark { - falls auf diese mittels einer URL verwiesen wird public static void main (String args[]) throws java.io.IOException, java.text.ParseException { - Klasse “URL” in java.net.* if (args.length != 2) System.exit(1); - auf höherem Niveau als die Socket-Programmierung - Sockets (mit TCP) werden vor dem Anwender verborgen benutzt // Create a bookmark for checking... CheckBookmark bm = new CheckBookmark(args[0], args[1]); bm.checkit(); // ...and check - Beispiel: zeilenweises Lesen einer Textdatei switch (bm.state) { case CheckBookmark.OK: System.out.println(“Local copy of “ + bm.url_string + “ is up to date”); break; case CheckBookmark.AGED: System.out.println(“Local copy of “ + bm.url_string + “ is aged”); break; case CheckBookmark.NOT_SUPPORTED: System.out.println(“Webserver does not support \ modification dates”); break; default: break; } - hier nicht gezeigt: Abfangen diverser Fehlerbedingungen! // Objekt vom Typ URL anlegen: hier Hostname angeben URL myURL; myURL = new URL(“http“, ... , “/Demo.txt“); ... Name der Datei DataInputStream instream; instream = new DataInputStream(myURL.openStream()); String line = ““; while((line = instream.readLine()) != null) // line verarbeiten ... } String url_string, chk_date; int state; - Es ist auch möglich, Daten an eine URL zu senden public final static int OK = 0; public final static int AGED = 1; public final static int NOT_SUPPORTED = 2; - POST-Methode, z.B. an Skript auf dem Server - Ferner: Information über das Objekt ermitteln CheckBookmark(String bm, String dtm) // Constructor { url_string = new String(bm); chk_date = new String(dtm); state = CheckBookmark.OK; } - z.B. Grösse, letztes Änderungsdatum, HTTP-Header etc. Vert. Sys., F. Ma. 140 Vert. Sys., F. Ma. 141 Adressierung public void checkit() throws java.io.IOException, java.text.ParseException { - Sender muss in geeigneter Weise spezifizieren, wohin die Nachricht gesendet werden soll URL checkURL = null; URLConnection checkURLC = null; - evtl. mehrere Adressaten zur freien Auswahl (Lastverteilung, Fehlertoleranz) try { checkURL = new URL(this.url_string); } catch (MalformedURLException e) { System.err.println(e.getMessage() + “: Cannot \ create URL from “ + this.url_string); return; } - evtl. mehrere Adressaten gleichzeitig (Broadcast, Multicast) - Empfänger ist evtl. nicht bereit, jede beliebige Nachricht von jedem Sender zu akzeptieren try { checkURLC = checkURL.openConnection(); checkURLC.setIfModifiedSince(60); checkURLC.connect(); } - selektiver Empfang (Spezialisierung) - Sicherheitsaspekte, Überlastabwehr - Probleme catch (java.io.IOException e) { System.err.println(e.getMessage() + “: Cannot \ open connection to “ + checkURL.toString()); return; } - Ortstransparenz: Sender weiss wer, aber nicht wo (sollte er i.Allg. auch nicht!) - Anonymität: Sender und Empfänger kennen einander zunächst nicht (sollen sie oft auch nicht) // Check whether modification date is supported if (checkURLC.getLastModified() == 0) { this.state = CheckBookmark.NOT_SUPPORTED; return; } // Cast last modification date to a “Date“ Date rem = new Date(checkURLC.getLastModified()); // Cast stored date of bookmark to Date DateFormat df = DateFormat.getDateInstance(); Date cur = df.parse(this.chk_date); // Compare and set flag for outdated bookmark if (cur.before(rem)) this.state = CheckBookmark.AGED; } } Vert. Sys., F. Ma. 142 Vert. Sys., F. Ma. 143 Kenntnis von Adressen? Direkte Adressierung - Adressen sind u.a. Geräteadressen (z.B. IP-Adresse oder Netzadresse in einem LAN), Portnamen, Socketnummern, Referenzen auf Mailboxes... - Direct Naming (1:1-Kommunikation): - Woher kennt ein Sender die Adresse des Empfängers? 1) Fest in den Programmcode integriert → unflexibel S send (...) to E Nachrichtenkanal E receive (...) [from S] 2) Über Parameter erhalten oder von anderen Prozessen mitgeteilt 3) Adressanfrage per Broadcast “in das Netz” - häufig bei LANs: Suche nach lokalem Nameserver, Router etc. 4) Auskunft fragen (Namensdienst wie z.B. DNS; Lookup-Service) Was aber ist ein Name? Woher kommt er? - “Konstante” bei Übersetzungszeit? - Referenz auf einen dynamischen Prozess? Hier vielleicht auch eine Menge von potentiellen Sendern? - Direct naming ist insgesamt relativ unflexibel - Empfänger (= Server) sollten nicht gezwungen sein, potentielle Sender (= Client) explizit zu nennen - Symmetrie ist also i.Allg. gar nicht erwünscht Vert. Sys., F. Ma. 144 Vert. Sys., F. Ma. 145 Indirekte Adressierung - Mailbox Mailbox-Kommunikation - Ermöglicht m:n-Kommunikation S1 . . . Sm send (...) to M M E1 . . . En receive (...) from M - Eine Nachricht hat i.Allg. mehrere potentielle Empfänger - Mailbox spezifiziert damit eine Gruppe von Empfängern - Kann jeder Empfänger die Nachricht bearbeiten? - Mailbox i.Allg. typisiert: nimmt nur bestimmte Nachrichten auf - Empfänger kann sich u.U. Nachrichten der Mailbox ansehen / aussuchen... - aber wer garantiert, dass jede Nachricht irgendwann ausgewählt wird? - Wo wird die Mailbox angesiedelt? (→ Implementierung) - als ein einziges Objekt auf irgendeinem (geeigneten) Rechner? - repliziert bei den Empfängern? (Abstimmung unter den Empfängern notwendig → verteiltes Cache-Kohärenz-Problem) - Nachricht verbleibt in einem Ausgangspuffer des Senders: Empfänger müssen sich bei allen (welche sind das?) potentiellen Sendern erkundigen Aus: “Nebenläufige Programme” von R. G. Herrtwich und G. Hommel (Springer-Verlag) - Mailbox muss eingerichtet werden: Wer? Wann? Wo? Vert. Sys., F. Ma. 146 Vert. Sys., F. Ma. 147 Indirekte Adressierung - Ports Kanäle und Verbindungen - m:1-Kommunikation - Neben Eingangsports (“in-port”) lassen sich auch Ausgangsports (“out-port”) betrachten - Ports sind Mailboxes mit genau einem Empfänger - Port “gehört” diesem Empfänger - Kommunikationsendpunkt, der die interne Empfängerstruktur abkapselt I2 - Ein Objekt kann i.Allg. mehrere Ports besitzen I1 P2 I1 O1 O2 I2 O1 thread Alternativ: Kanäle benennen und etwas auf den Kanal senden bzw. von ihm lesen - Ports können als Ausgangspunkte für das Einrichten von Verbindungen (“Kanäle”) gewählt werden P3 receive (...) from P1 - Dazu werden je ein in- und out-Port miteinander verbunden. Dies kann z.B. mit einer connectAnweisung geschehen: connect p1 to p2 Pragmatische Aspekte (Sprachdesign etc.): - Sind Ports statische oder dynamische Objekte? - denkbar sind auch broadcastfähige Kanäle - Wie erfährt ein Objekt den Portnamen eines anderen (dynamischen) Objektes? - Die Programmierung und Instanziierung eines Objektes findet so in einer anderen Phase statt als die Festlegung der Verbindungen Konfigura- - können Namen von Ports verschickt werden? - Sind Ports typisiert? - Grössere Flexibilität durch die dynamische Änderung der Verbindungsstruktur - würde den selektiven Nachrichtenempfang unterstützen - Grösse des Nachrichtenpuffers? Vert. Sys., F. Ma. tionsphase - Kommunikationsbeziehung: wahlweise 1:1, n:1, 1:n, n:m - Können Ports geöffnet und geschlossen werden? - genaue Semantik? O2 P1 I1 send (...) to P2 O1 send (...) to O2; receive (...) from I1; 148 Vert. Sys., F. Ma. 149