Der Server - The Distributed Systems Group

Werbung
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
Herunterladen