HSG |
|
In den RFCs ist ein Socket als ein Tupel aus Ziel- und Quell-IP-Adresse, Ziel- und Quell-Port und
Netzwerkprotokoll beschrieben.
(aus 'Socket' bei wikipedia - inzwischen geändert!)
Der Ziel-Port wählt einen bestimmten Dienst aus, der Quell-Port wird zufällig generiert. Auf diese Weise sind mehrere
Verbindungen zu einem Dienst zwischen zwei Rechnern
(hosts) möglich.
Wir benutzen ein Socket (engl. Steckdose, Buchse) als eine Software-Schnittstelle zum Zweck der
Nutzung einer bidirektionalen Verbindung zwischen Computern. Diese Schnittstelle wird in
objektorientierten Sprachen als ein spezielles Objekt auftreten, das die Handhabung erleichtert.
Man kann das Socket-Konzept mit einer Telefonverbindung vergleichen. Das Telefongerät entspricht dem Socket. Die IP-Adressen wären die Telefonnummern, die Ports wären die Nummern der Durchwahlverbindungen. Um einen Telefon-Dienst in Anspruch nehmen zu können, muss man also die Nummer samt Durchwahl des Dienstes kennen. Gewöhnlich meldet sich 'der Dienst' nach einer Anwahl und nach Austausch von 'Anmeldeinformationen' steht die Verbindung solange bis einer der beiden Teilnehmer auflegt. Nach dem Zustandekommen ist eine Verbindung symmetrisch und fullduplex, dh. beide Teilnehmer können gleichzeitig 'senden'. Was wird passieren, wenn man, nachdem der Angerufene abgehoben hat, eine Zeitlang nichts sagt? Die eigene Nummer muss man übrigens beim Anrufen nicht kennen. Hat man mehrere Telefone, so kann man in der Regel auch mehrere Verbindungen gleichzeitig zu einem Dienst z.B. einem Call-Center aufbauen.
Zunächst wollen wir mit einem Client-Socket Verbindung zu einem Serverdienst aufnehmen. So bietet z.B. unter der URL ts1.univie.ac.at ein Zeitserver auf Port 37 seine Dienste an.
>>> import socket >>> cs = socket.socket() >>> cs.connect(('ts1.univie.ac.at',37)) >>> cs.recv(100) b'\xd0\xeb\xe95' >>> cs.recv(100) b'' >>> list(b'\xd0\xeb\xe95') [208, 235, 233, 53] >>>
Zunächst wurde ein Client-Socket-Objekt erzeugt. Keine Parameter dabei anzugeben, heißt, sich auf die Python-Vorgaben verlassen, was hier durchaus passt. Dann kommt der Verbindungsaufbau. Die Adresse ist in Python ein Tupel (host,port). host ist ein String wie 'www.hsg-kl.de' oder '20.171.3.26', port in eine integer-Zahl. Kommt die Verbindung zustande, kann man versuchen aus dem Empfangspuffer z.B. 100 Bytes auszulesen. Im Beispiel wurden nur die 4 Bytes \xd0, \xeb, \xe9 und ord(5) zurückgegeben. Hier muss man die spezielle Art, wie Python Byte-Strings defaultmäßig ausgibt, bedenken. Ein Typecast macht eine 'schöne' Liste daraus. Die Bytes bedeuten übrigens in big-endian-Darstellung die Anzahl der seit 1.1.1900, 0:00h vergangenen Sekunden.
>>> 208*256**3 + 235*256**2 + 233*256 + 53 3505121589 >>> (111*365.25+27)*24*60*60 3505226400.0 >>>
Eine kleine Abschätzung stützt diese Überlegung.
Importiere das Modul time. Was ist in der Hilfe ( >>>help(time) ) mit 'Epoch' gemeint? Ermittle mit Hilfe von ctime aus dem Modul die aktuelle Zeit. Die Konstante TIME1970 = 2208988800 dürfte dabei nützlich sein. Was bedeutet sie?
Ermittle die ip-Adresse von ts1.univie.ac.at. Auch hier findet man mit help(socket) eine passende Funktion. Lösung: '131.130.250.250'
Ein HTTP-Server reagiert nur auf bestimmte Befehle. So sollte man sich in folgendem Beispiel beim send-Befehl nicht vertippen, sonst reagiert der Server nicht und man wartet ewig. Wie ist das eigentlich zu interpretieren, wenn ein recv-Befehl - wie am Schluss des Beispiels - sofort zurückkommt?
>>> import socket >>> cs = socket.socket() >>> cs.connect(('www.hsg-kl.de',80)) >>> cs.send(bytes('GET /abc.html HTTP/1.1\r\nHost: www.hsg-kl.de\r\n\r\n','ASCII')) 47 >>> b=cs.recv(1024) >>> print(str(b,'ASCII')) HTTP/1.1 200 OK Date: Thu, 27 Jan 2011 14:02:09 GMT Server: Apache/2.2.9 (Debian) mod_fastcgi/2.4.6 PHP/5.2.6-1+lenny9 with Suhosin-Patch mod_python/3.3.1 Python/2.5.2 Last-Modified: Wed, 08 Dec 2004 17:14:12 GMT ETag: "800091-c5-3eabdd9af6100" Accept-Ranges: bytes Content-Length: 197 Content-Type: text/html <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN"<html> <head> <title>Test</title> </head> <body> <p> Das kommt vom HSG! </p> </body> </html> >>> b=cs.recv(1024) >>>
Hole wie im Beispiel von einem HTTP-Server deiner Wahl die Bytes einer Datei.
Ein Server ist etwas schwieriger zu programmieren, weil hier mindestens zwei Sockets beteiligt sind. Man braucht einen Verbindungssocket zum Aufbau der Verbindung und (mindestens) einen Kommunikationssocket. Man kann sich vorstellen, dass der Verbindungssocket am Telefon sitzt und abhebt und die Verbindung sofort auf ein weiteres Telefon (Kommunikationssocket) umstöpselt. Er tut das, um sofort weitere Verbindungen annehmen zu können. Zum Experimentieren kann man den Server auf der einen und den Client auf einer weiteren Shell aufbauen.
Server-Shell
>>> s = socket.socket() >>> s.bind(('',12345)) >>> s.listen(1) >>> (conn,addr) = s.accept()
bind bindet eine Adresse an den Socket. Gibt man keine ip an, so reagiert Python auf alle vorhandenen Interfaces. Der Parameter bei listen gibt die maximale Anzahl der Verbindungen an, die der Server annehmen kann. accept gibt schließlich die Referenz auf einen Verbindungssocket und die Adresse des Clients an. Im vorliegenden Fall hatte addr den Wert ('127.0.0.1', 34354), dh. auch der Client hat eine - zufällig erzeugte - Portnummer. Mit Hilfe dieser Portnummer kann der Server die verschiedenen Pakete zur richtigen Verbindung zuordnen.
Client-Shell
>>> import socket >>> c = socket.socket() >>> c.connect(('localhost',12345)) >>> c.recv(1024) |
Server-Shell
>>> conn.send(bytes('Hallo','Ascii')) 5
Client-Shell
b'Hallo' >>>
Es ist schön zu sehen, wie der recv-Befehl die Client-Shell solange blockiert, bis die Server-Shell gesendet hat. Das passiert natürlich auch in der umgekehrten Richtung.
Server-Shell
>>> conn.recv(1024) |
Client-Shell
>>> c.send(bytes('Welt','ASCII')) 4 >>>
Server-Shell
b'Welt' >>>
Jeder der beiden Verbindungspartner kann nun die Verbindung schließen, z.B. der Server.
Client-Shell
>>> c.recv(1024) |
Server-Shell
>>> conn.close() >>>
Client-Shell
b'' >>>
Man sieht, dass eine geschlossene Verbindung einen Socket bei recv sofort mit 0 Bytes zurückkehren lässt.
Client-Shell
>>> c.send(bytes('abc','ascii')) 3 >>>
Man sieht auch, dass ein nicht geschlossener Socket bei beendeter Verbindung ohne Fehlermeldung 'ins Leere' sendet.
Baue mit Hilfe von Sockets eine Verbindung zwischen verschiedenen Computern auf. Sollte der Server-Computer hinter einem Router stehen, so muss man bei dem Router eine Portweiterleitung einschalten. Mit Hilfe der Seite www.canyouseeme.org kann man einerseits testen, ob man erfolgreich war, andererseits sieht man auch die Router-Adresse.
Nach Aufbau einer Verbindung kann man die beteiligten ip-Adressen ermitteln.
>>> import socket >>> s = socket.socket() >>> s.connect(('www.hsg-kl.de',80)) >>> s.getpeername() ('131.246.120.81', 80) >>> s.getsockname() ('192.168.2.105', 50790) >>> s.close()
Vergleiche die Ausgabe obigen Befehls mit der Auflistung der Interfaces im jeweiligen Betriebssystem (Windows: ipconfig /all, Linux: ifconfig, Mac: ifconfig -a).
---------------------------------------------------------------------------------------------------
Ein Taster auf dem Client-Rechner soll die LED auf dem Server-Rechner ansteuern. Beim Testen der Programme ist zu beachten, dass die Server-Adresse im Client 'hardgecodet' werden muss und dass wegen des Blockens der sockets die GUIs erst nach dem Verbindungsaufbau auftauchen. Es ist ein anregendes Problem, wie sich dieses Verhalten verbessern lässt.
---------------------------------------------------------------------------------------------------
Marco Schneider,Gymnasium Kusel hat einen Python-Client gebaut.
Bei Thomas Karp, Friedrich-Magnus-Schwerd Gymnasium Speyer kann man in Sachen Java fündig werden.
Die Menge aller öffentlichen Signaturen einer Klasse gehört zur Schnittstelle der Klasse. Auch selbstdefinierte Datentypen und Konstanten, die die Klasse benutzt, gehören zur Schnittstelle. Zusätzlich enthält eine Schnittstelle semantische Festlegungen. Diese werden oft informell in Kommentaren oder durch selbsterklärende Methodennamen geliefert.
Grob gesagt: Eine Schnittstelle einer Klasse enthält alles, was ein Nutzer über die Klasse wissen muss.
erstellt mit violet
Ein Objekt vom Typ TNetzVerbinder kann als Server oder als Client fungieren. Die Methode starteServerdienst(port : string) macht das Objekt zum Server, die Methode verbindeZu(host, port : string) zum Client. Eine eingegangene Nachricht kann mitGetNachricht : string ausgelesen werden. Das Ereignis OnNeueNachricht wird nach dem Eintreffen einer neuen Nachricht ausgelöst. TEreignis = procedure of object ist der Typ einer parameterlosen Prozedur. Fehlermeldungen werden mit SendMessage in einem eigenständigen Fenster ausgegeben, sodass die Klasse keinerlei Bindung zu einer GUI haben muss. Über die Ereignisse kann aber eine solche Verbindung hergestellt werden.
erstellt mit UMLEd, Download: TNetzVerbinder.zip
NetzVerbindung_exe.zip, NetzVerbindung.zip
Es soll nun schrittweise mit Hilfe der Klasse TNetzVerbinder ein kleines Client-Programm entwickelt werden. Dabei kann zur Zeitersparnis das Projekt Client1 (in Client10.zip) als Vorlage dienen.
netz.OnNeueNachricht := aktualisiere;Da OnNeueNachricht eine prozedurale Variable ist, ist das eine einfache Wertzuweisung.
Der einfache Client kann nun zur Inanspruchnahme von Serverdiensten genutzt werden. Man muss dazu die Adresse des Servers und die Portnummer des Dienstes wissen. Außerdem wird die Kommunikation mit einem Server nach einem bestimmten Protokoll, z.B. Hypertext Transfer Protocol-http abgewickelt. Die Einzelheiten eines Protokolls z.B. wie man eine html-Datei von einem Web-Server holt:
GET /abc.html HTTP/1.1
Host: www.hsg-kl.de
(Achtung: 2 CRLF am Ende!)
erfährt man aus den RFCs. Die RFCs sind in der Regel sehr umfangreich und technisch-korrekt formuliert. Über wikipedia kommt man gewöhnlich einfacher an die benötigten Informationen.
Das Client1-Programm lässt sich einfach zu einem kleinen Server-Programm umbauen. Das Server-Programm kann allerdings nur eine Verbindung annehmen. Folgende Änderungen sind hierfür duchzuführen
netz.starteServerDienst(port);
Programmiere einen Time-Client und decodiere die empfangene Information.
Wer sich für eine erweiterte Socket-Programmierung insbesondere für Multi-Client-Server mit der Klasse TWSocket, kann sich hier umsehen.