16.8 Socket-Programmierung
Die URL-Verbindungen sind schon High-level-Verbindungen, wir müssen uns nicht erst um Übertragungsprotokolle wie HTTP oder - noch tiefer - TCP/IP kümmern. Aber alle höheren Verbindungen bauen auf Sockets auf, und auch die Verbindung zu einem Rechner über URL ist mit Sockets realisiert. Beschäftigen wir uns also nun etwas mit dem Hintergrund.
16.8.1 Das Netzwerk ist der Computer
Die Rechner, die im Internet verbunden sind, kommunizieren über Protokolle, wobei TCP/IP das wichtigste geworden ist. Die Entwicklung des Protokolls geht in die Achtzigerjahre zurück. Die ARPA (Advanced Research Projects Agency) gab der Universität von Berkeley (Kalifornien) den Auftrag, unter Unix das TCP/IP-Protokoll zu implementieren, um dort in dem Netzwerk zu kommunizieren.1 Was sich die Kalifornier ausgedacht hatten, fand auch in der Berkeley Software Distribution (BSD), einer Unix-Variante, Verwendung: die Berkeley-Sockets. Mittlerweile hat sich das Berkeley-Socket-Interface über alle Betriebssystemgrenzen hinweg entwickelt und ist der De-facto-Standard für TCP/IP-Kommunikation. Es finden sich in allen möglichen Unix-Derivaten und auch unter Windows Socket-Implementierungen. So ist Windows Socket ein Interface für Microsoft Windows, mit dem sich die Sockets auch unter dieser Plattform nutzen lassen. Die Spezifikation von Windows Socket basiert auf BSD Unix Version 4.3.
Ein Socket dient zur Abstraktion und ist ein Verbindungspunkt in einem TCP/IP-Netzwerk. Werden mehrere Computer verbunden, so implementiert jeder Rechner einen Socket: Derjenige, der Daten empfängt (Client), öffnet eine Socket-Verbindung zum Horchen, und derjenige, der sendet, öffnet eine Verbindung zum Senden (Server). Es lässt sich in der Realität nicht immer ganz trennen, wer Client und wer Server ist, da Server ebenfalls zum Datenaustausch Verbindungen aufbauen können. Doch für den Betrachter von außen ist der Server der Wartende und der Client derjenige, der die Verbindung initiiert.
Damit der Empfänger den Sender auch hören kann, muss dieser durch eine eindeutige Adresse als Server ausgemacht werden. Er bekommt also eine IP-Adresse im Netz und eine ebenso eindeutige Port-Adresse. Der Port ist so etwas wie eine Zimmernummer im Hotel. Die Adresse bleibt dieselbe, aber in jedem Zimmer sitzt einer und macht seine Aufgaben.
Für jeden Dienst (Service), den ein Server zur Verfügung stellt, gibt es einen Port. Diese Adressen sind aber nicht willkürlich vergeben, sondern werden von der IANA2 (Internet Assigned Numbers Authority) vergeben. Die IANA ging aus der ISOC (Internet Society) und der FNC (Federal Network Council) hervor. Die Internet Authority ist der zentrale Koordinator für die IP-Adressen, Domain-Namen, MIME-Typen und für viele andere Parameter, unter anderem auch für die Port-Nummern - Näheres unter http://www.iana.org/.
Eine Port-Nummer ist eine 16-Bit-Zahl und in die Gruppen »System« und »Benutzer« eingeteilt. Die so genannten Well-Known-System-Port- (auch Contact Port-) Nummern liegen im Bereich von 0-1023. (Noch vor einigen Jahren haben 255 definierte Nummern ausgereicht.) Die User-Ports umfassen den restlichen Bereich von 1024-65535. Die folgende Tabelle zeigt einige wenige dieser Port-Nummern. Die vollständige Liste ist unter http://www.iana.org/assignments/port-numbers verfügbar.
Service
|
Port
|
Beschreibung
|
echo
|
7
|
Echo
|
daytime
|
13
|
Daytime
|
qotd
|
17
|
Quote of the Day
|
ftp-data
|
20
|
File Transfer [Default Data]
|
ftp
|
21
|
File Transfer [Control]
|
ssh
|
22
|
SSH Remote Login Protocol
|
telnet
|
23
|
Telnet
|
smtp
|
25
|
Simple Mail Transfer
|
time
|
37
|
Time
|
nicname
|
43
|
Who Is
|
domain
|
53
|
Domain Name Server
|
whois++
|
63
|
whois++
|
gopher
|
70
|
Gopher
|
finger
|
79
|
Finger
|
www
|
80
|
World Wide Web HTTP
|
www-http
|
80
|
World Wide Web HTTP
|
pop2
|
109
|
Post Office Protocol - Version 2
|
pop3
|
110
|
Post Office Protocol - Version 3
|
pwdgen
|
129
|
Password Generator Protocol
|
Tabelle 16.2 Einige ausgewählte System-Ports
Die auf einem Unix-System installierten Ports sind meistens unter /etc/services einzusehen.
16.8.2 Standarddienste unter Windows nachinstallieren
Wir wollen nun mit der Netzwerkprogrammierung bei den Clients beginnen. Diese greifen auf einen Server zu und nutzen dessen Dienste. Ein solcher Dienst wäre etwa ein Zeit-Server oder ein Echo-Server. Der Zeit-Server liefert die aktuelle Tageszeit, und ein Echo-Server würde alle gesendeten Zeichenketten wieder zurückschicken. Wenn wir allerdings einen Dienst auf einem Rechner nutzen wollen, dann muss dieser auch angeboten werden. Unter Unix gibt es eine ganze Menge von Standarddiensten. Allerdings haben die Leser, die mit Windows arbeiten, überhaupt keine Dienste (ein Portscann zeigt dies leicht). Um Unix- und Windows-Leser auf den gleichen Stand zu bringen, wollen wir unter Windows zwei Standarddienste installieren. Diese sind unter Windows abhängig von WinSock (http://www.sockets.com/). WinSock ist die Abkürzung für Windows Socket und wird immer dann benötigt, wenn ein Computer in einem lokalen Netzwerk und über Modem an das Internet soll. Eine manuelle Installation von WinSock ist nicht mehr nötig; TCP/IP sollte aber auf dem Rechner installiert sein. Da sich viele Rechner jedoch im Internet oder im Netz befinden, ist dies vermutlich die Standardkonfiguration. Uns interessieren aber die Dienste. Sie bauen auf WinSock auf, und unter der Web-Seite »WinSock Development Information« finden wir unter »Sample Applications« einen Verweis (ftp://ftp.sockets.com/sockets/wsa_all.zip) auf das Paket mit den Quellcodes. Dieses Paket ist etwas über ein Megabyte groß und enthält mehr als wir benötigen.3 Daher habe ich nur die die ausführbaren Programme unter http://java-tutor.com/download/internet/WindowsSocketsBinaries.zip abgelegt. Das Archiv hat eine Größe von 196 KB.
16.8.3 Stream-Sockets
Ein Stream-Socket baut eine feste Verbindung zu einem Rechner auf. Das Besondere dabei ist: Die Verbindung bleibt für die Dauer der Übertragung bestehen. Dies ist bei der anderen Form der Sockets, den Datagram-Sockets, nicht der Fall. Wir behandeln die Stream-Sockets zuerst.
Eine Verbindung zum Server aufbauen
Um Daten von einer Stelle zur anderen zu schicken, muss zunächst eine Verbindung zum Server bestehen. Dieser wiederum beantwortet die eingehenden Fragen. Mit den Netzwerkklassen unter Java lassen sich sowohl Client- als auch Server-basierte Programme schreiben. Da die Client- noch einfacher als die Server-Seite ist - in Java ist Netzwerkprogrammierung ein Genuss - beginnen wir mit dem Client. Dieser muss zu einem horchenden Server verbunden werden. Diese Verbindung wird durch die Socket-Klasse aufgebaut:
Socket clientSocket = new Socket( "die.weite.welt", 80 );
Der erste Parameter des Konstruktors ist der Name des Servers (Host-Adresse), mit dem wir uns verbinden wollen. Der zweite Parameter ist der Port, wir haben hier 80 gewählt, um zu einem Web-Server Verbindung aufzubauen.
Hinweis Verbinden wir ein Applet mit dem Server, von dem es geladen wurde, würden wir mit getCodeBase().getHost() arbeiten.
Socket server = new Socket( getCodeBase().getHost(), 7 );
|
Es gibt noch eine andere Möglichkeit, zu einem Host zu gelangen: über die Klasse InetAddress.
secondSocket = new Socket( server.getInetAddress(), 1234 );
Alternativ ermittelt die Funktion getHostByName(String) die InetAddress eines Hosts. Ist der Server nicht erreichbar, so wirft das System bei allen Socket-Konstruktionsversuchen eine UnknownHostException aus.
|
Socket() throws IOException
Erzeugt einen nicht verbundenen Socket. Es ist der Standard SocketImpl. |
|
Socket( SocketImpl impl ) throws IOException
Erzeugt einen unverbundenen Socket mit einer benutzerdefinierten SocketImpl. |
|
Socket( String host, int port ) throws IOException
Erzeugt einen Stream-Socket und verbindet ihn mit der Port-Nummer am angegebenen Host. |
|
Socket( InetAddress address, int port ) throws IOException
Erzeugt einen Stream-Socket und verbindet ihn mit der Port-Nummer am Host mit der angegebenen IP-Nummer. |
|
Socket( String host, int port, InetAddress localAddr, int localPort )
throws IOException
Erzeugt einen Socket und verbindet ihn zum Host am Port. |
|
Socket( InetAddress address, int port,.InetAddress localAddr, int localPort )
throws IOException
Erzeugt einen Socket und verbindet ihn zum Host am Port. |
Die Verbindung wieder abbauen
Die Methode close() leitet das Ende einer Verbindung ein und gibt dem Betriebssystem die reservierten Handles zurück. Ohne Freigabe könnte das Betriebssystem unter Umständen nach einer gewisssen Zeit keine Handles mehr zurückgeben, und weiteres Arbeiten wäre nicht möglich. Dies geht so weit, dass auch der Browser keine HTML-Seite mehr vom Server bekommt. Taucht also das Verhalten auf, dass einige Verbindungen aufgebaut werden können, danach aber Schluss ist, sollte diese Lücke untersucht werden.
|
void close() throws IOException
Schließt den Socket. |
Server unter Spannung: die Ströme
Besteht erst einmal die Verbindung, so wird mit den Daten vom Server genauso verfahren wie mit den Daten aus einer Datei. Die Socket-Klasse liefert uns Streams, mit denen wir lesen und schreiben können. Nun bietet die Klasse Socket die Methoden getInputStream() und getOutputStream(), die einen Zugang zum Datenstrom erlauben. Holen wir uns zunächst einen Ausgabestrom vom Typ OutputSteam:
OutputStream out = server.getOutputStream()
Oft wird dieser dann noch schnell zu einem DataOutputStream oder PrintStream beziehungsweise PrintWrtier gemacht, damit die Ausgabemöglichkeiten vielfältiger sind. Genauso wird mit dem Eingabestrom verfahren. Wandeln wir ihn gleich in einen BufferedReader um:
BufferedReader in = new BufferedReader(
new InputStreamReader( server.getInputStream()) );
Wir kennen das Prinzip schon von den URL-Verbindungen.
|
InputStream getInputStream() throws IOException
Liefert den Eingabestrom für den Socket. |
|
OutputStream getOutputStream() throws IOException
Liefert den Ausgabestrom für den Socket. |
16.8.4 Informationen über den Socket
Wie beim URL-Objekt, so lassen sich auch beim Socket keine grundsätzlich wichtigen Parameter nachträglich ändern. Port-Adresse wie auch das Ziel müssen beim Erzeugen bekannt sein. Aber, wie bei einer URL auch, es lassen sich Informationen über das Socket-Objekt einholen.
|
InetAddress getInetAddress()
Liefert die Adresse, mit der der Socket verbunden ist. |
|
InetAddress getLocalAddress()
Liefert die lokale Adresse, an die der Socket gebunden ist. |
|
int getPort()
Gibt den Remote-Port zurück, mit dem der Socket verbunden ist. |
|
int getLocalPort()
Gibt den lokalen Port des Sockets zurück. |
Hier klicken, um das Bild zu Vergrößern
16.8.5 Mit telnet an den Ports horchen
Wir können zum Server mittels des Kommandozeilenprogramms telnet eine Verbindung aufbauen und die Funktionalität eines Servers einfach prüfen, wenn dieser auf ASCII-Kommandos antwortet. Die Signatur von telnet ist:
$ telnet IP Port
Mit dieser Technik können wir uns zum Beispiel direkt mit dem FTP-Server verbinden, ohne ein Frontend wie ftp zu nutzen, oder auch ein Echo-Kommando absetzen, um damit den Server auf Erreichbarkeit zu testen.
16.8.6 Ein kleines Echo - lebt der Rechner noch?
Hier klicken, um das Bild zu Vergrößern
Möchten wir überprüfen, ob ein Rechner in der Lage ist, Kommandos über seine Netzwerkschnittstelle entgegenzunehmen, so können wir ein Kommando hinschicken und warten, ob etwas passiert. Am einfachsten ist der Aufbau zu dem Echo-Server, ein Service, der alle ankommenden Kommandos gleich wieder zurückschickt.
Wenn wir den Echo-Dienst nutzen wollen, dann senden wir ein Testwort zum Server und überprüfen, ob das gleiche Wort wieder zurückkommt. Die Herstellung der Verbindung zum Echo-Server ist mit der Socket-Klasse kein Problem. Da der Echo-Service immer an Port 7 liegt, eröffnet die Anweisung Socket(IPAdress, 7) die Verbindung. Anschließend lassen sich InputStream und OutputStream holen, und die Anfrage lässt sich verarbeiten. Die IP-Adresse lesen wir aus der Kommandozeile.
Hinweis Ein solcher Echo-Server ist oft unter Unix aktiv, unter Windows und anderen Systemen jedoch nicht. Wir können daher nur zu einem Rechner die Verbindung aufbauen und unser Programm prüfen, wenn dieser Dienst auch tatsächlich läuft. Wenn wir lokal auf einem Windows-Rechner arbeiten, dann können wir uns einige primitive Dienste durch Hilfsprogramme installieren. Die Quelle wurde oben schon genannt und ist http://java-tutor.com/download/internet/WindowsSocketsBinaries.zip. Läuft der Echo-Dienst unter einem Rechner nicht, dann wird eine »java.net.ConnectException: Connection refused: no further information«-Ausnahme angezeigt.
|
Listing 16.12 Echo.java
import java.io.*;
import java.net.*;
class Echo
{
public static void main( String args[] ) throws Exception
{
Socket t = new Socket( args[0], 7 );
BufferedReader in = new BufferedReader(
new InputStreamReader( t.getInputStream()) );
PrintStream os = new PrintStream( t.getOutputStream() );
String test = "Superkalifragilistischexpialigetisch";
os.println( test );
String s = in.readLine();
if ( s.equals(test) )
System.out.println( "Hurra, er lebt!" ) ;
t.close();
}
}
1 Die Geschichte, dass das Internet nur deshalb entwickelt wurde, damit bei Rechnerausfällen durch kriegerische Aktivitäten weiter Kommunikation möglich ist, ist so falsch. Larry Roberts sagt dazu, dass die Entwickler dem Ministerium die Vorteile des Internets nur deswegen so verkauften, damit sie mehr Forschungsgelder bekamen.
2 Nicht zu verwechseln mit »Illinois Association of Nurse Anesthetists« beziehungsweise »Intermodal Association of North America«!
3 Die einzelnen Programme liegen allerdings auch unter>http://www.sockets.com/sample.htm.
|