1 - Institut für Betriebssysteme und Rechnerverbund

Werbung
1
1
Technische Universität Braunschweig
Institut für Betriebssysteme und Rechnerverbund
Kommunikation und Multimedia
Prof. Dr. L. Wolf
Praktikum Computernetze im WS07/08
– Begleitdokumentation –
Betreuer: Zefir Kurtisi, Habib-ur-Rehman, NN
http://www.ibr.cs.tu-bs.de/courses/ws0708/pcn/
Kurzbeschreibung
In diesem Praktikum sollen die in der Vorlesung ,,Computernetze” vermittelten Inhalte praktisch
angewandt werden. Es sind dabei mehrere Aufgaben mit vorgegebenen Fristen zu implementieren
und von den Betreuern nach erfolgreicher Demonstration abzunehmen.
Programmiert wird in C oder C++, teilweise sind bestehende C++ Programmteile zu erweitern.
Ablauf
In einer Einführungsveranstaltung zum Semesterbeginn werden Formalitäten geklärt und die Teilnehmer in Zweiergruppen aufgeteilt. Jede Gruppe bearbeitet alle Aufgaben, wobei eine Arbeitsteilung vorgesehen ist. Diese erfolgt gruppenintern – beispielsweise nach Funktionsblöcken oder
Teilkomponenten – und muss deutlich dokumentiert werden.
Es sind drei bis vier Aufgaben vorgesehen, die jeweils Bearbeitungszeiten von drei bis fünf Wochen
erfordern. Es gibt keinen vorgegebenen Zeitplan, alle Gruppen müssen zum Ende der Veranstaltung
alle Aufgaben implementiert und erfolgreich vorgeführt haben.
Die Quelltexte der lauffähigen Programme müssen den Betreuern vor der Abnahme zur Prüfung
per Email zugesandt werden. Die Abnahme selbst erfolgt während der betreuten Zeit und spätestens zur genannten Frist. Zur Abnahme müssen beide bzw. alle Gruppenmitglieder anwesend sein.
Jeder Teilnehmer wird dabei im Gespräch mit den Betreuern seinen Anteil an der Entwicklung
dokumentieren und Fragen beantworten. Eine Aufgabe gilt als abgenommen, wenn die geforderte
Funktionalität fehlerfrei nachgewiesen wurde und das Gespräch zufriedenstellend verläuft.
Das Praktikum gilt als bestanden, wenn alle Aufgaben erfolgreich bearbeitet wurden.
Arbeitsgerät
Für die Programmierung stehen im CIP-Pool G40 etwa 20 iMacs von Apple zur Verfügung. Diese
werden für das Praktikum an einem betreuten und zwei unbetreuten Terminen in der Woche reserviert. In der übrigen Zeit können die Rechner benutzt werden, solange sie nicht von Teilnehmern
anderer Praktika benötigt werden.
Der Zugang zu den Rechnern und dem Raum G40 erfolgt über einen Account, der mit der yNummer und dazugehörigem Passwort über die entsprechende Webseite1 einmalig eingerichtet
wird.
Das Betriebssystem OS/X basiert auf einem UNIX-Kern, so dass evtl. vorhandene Erfahrung mit
Linux anwendbar sind. Für die Entwicklung werden teilweise Programmgerüste vorgegeben, die
1
Aktivierung von RZ-Accounts für den CIP-Pool über http://www.ibr.cs.tu-bs.de/passwd/rz.html
2
um die geforderten Funktionen erweitert und über Makefiles übersetzt werden. Alternativ steht
es jedem Teilnehmer frei, eine der integrierten Entwicklungsumgebungen XCode oder Eclipse zu
verwenden.
Die vorgegebenen Programmgerüste sind so ausgelegt, dass eine Entwicklung teilweise auch am
heimischen Rechner erfolgen kann. Zu berücksichtigen ist hierbei allerdings, dass es sich bei den
– auf PowerPC basierenden – iMacs um Big-Endian Rechner handelt, während die meisten PCs
daheim Little-Endian x86-Maschinen sind.
Unabhängig davon, wo entwickelt wurde, müssen für die Abnahme die Programme zu allen Aufgaben auf den iMacs lauffähig sein.
Ergänzend zu den auf der Veranstaltungsseite genannten Dokumentation folgt nun ein erster
Einblick in die Thematik von
• Internet-Protokollen (Inhalt sollte aus der KS-Vorlesung bekannt sein)
• Socket-Programmierung
3
Internet–Protokolle
Kommunikationsprotokolle für Netzwerke müssen eine Vielzahl unterschiedlicher Funktionen erfüllen
(z.B. Adressierung des Zielrechners, Flußkontrolle, ggf. Übertragungswiederholungen etc.). Weil
ein einziges Protokoll mit allen Eigenschaften zu komplex und unübersichtlich wäre, existieren für
die verschiedenen Aufgaben jeweils eigene Protokolle. Diese werden in den folgenden Abschnitten
kurz vorgestellt.
Die Internet Protokoll-Familie (TCP/IP)
Das Internet Protokoll (IP) hat sich aus dem ARPANET entwickelt. Die zum IP-Protokoll gehörende Protokollfamilie schaffte den weltweiten Durchbruch, als sie Bestandteil des Berkeley UNIX
wurde. Sämtliche Protokolldefinitionen sind als Request for Comments (RFC) direkt über das
weltweite Internet verfügbar.
Das Internet-Protokoll stellt ein zentrales Protokoll dar. Die grundlegende Aufgabe besteht in der
Vermittlung der Daten zwischen Sender und Empfänger, die in verschiedenen Teilnetzen angesiedelt sein können.
Anwendungen
FTP Telnet
RPC
DVMRP
ICMP
UDP
TCP
IGMP
...
IP
ARP
Netzwerk
Fig. 1: Internet-Protokolle
Abbildung 1 zeigt die geschichtete Anordnung der Protokolle im Internet. Die Grundidee des IPProtokolls ist die Schaffung eines gemeinsamen, netzübergreifenden Datagramm-Dienstes, der sich
auf ganz verschiedenen Übertragungsmedien implementieren lässt. Dadurch wurde erreicht, dass
sehr unterschiedliche existierende Netzwerke (vom einfachen seriellen Kabel bis hin zu SatellitenStrecken) zur Übertragung benutzt werden können, was überhaupt erst die Schaffung eines weltweiten Netzwerks ermöglichte.
Die einzelnen Schichten haben folgende Aufgaben:
Anwendungsschicht: Auf dieser Schicht findet man die Anwendungen, die Dienste der unterliegenden Schichten nutzen. Die Daten dieser Dienste werden unabhängig von der Form an
die Transportschicht weitergereicht. Beispielprotokolle sind Remote Procedure Call (RPC),
E-Mail (SMTP), File Transfer (FTP) oder remote Terminal (Telnet bzw. rlogin).
Transportschicht: Die primäre Aufgabe dieser Schicht ist es, die Verbindung von einem Anwendungsprogramm zu einem anderen herzustellen, also die Ende-zu-Ende Kommunikation. In
dieser Schicht existieren zwei Transport-Protokolle: TCP (Transmission Control Protocol)
stellt einen verbindungsorientierten und UDP (User Datagram Protocol) einen verbindungslosen Dienst zur Verfügung.
4
Normalerweise werden mehrere Anwendungen gleichzeitig mit der Transportschicht kommunizieren. Die Transportschicht muss die verschiedenen Daten der Anwendungen auseinanderhalten und entsprechend verteilen können. Mit Hilfe von Portnummern werden die einzelnen
Anwendungen auf einem Rechner adressiert.
Vermittlungsschicht: Das Internet-Protokoll (IP) erlaubt die Kommunikation zwischen zwei oder
mehreren Rechnern. Die Vermittlungsschicht erhält Datenpakete aus der Transportschicht,
wandelt sie in IP-Datagramme um und stellt die Daten entweder direkt oder über einen
oder mehrere Router dem Empfänger zu. Zur Adressierung eines Rechners werden 32-bit
(oder 4 Byte) lange IP-Adressen benutzt, die gewöhnlich dezimal durch Punkte getrennt
dargestellt werden (z.B. 134.169.34.12). In der Version 6 des IP-Protokolls wurden diese
Adressen auf 128-bit (oder 16 Byte) erweitert (z.B. 3ffe:400:90:10::1). Anhand der Adresse
kann IP – unter der Benutzung von Routingtabellen – entscheiden, wie der Zielrechner zu
erreichen ist bzw. welcher Rechner der nächste Knoten auf dem Weg zum Zielrechner ist.
Netzwerkschicht: Hier werden die IP-Datagramme von den oberen Schichten in Empfang genommen und über das entsprechende Netzwerk geschickt. Erst auf dieser Ebene spielt der Typ
des verwendeten Netzes (z.B. Ethernet) eine Rolle. Diese Schicht besteht entweder aus einem
simplen Devicetreiber (z.B. falls sich die Empfängermaschine im gleichen LAN befindet) oder
einem etwas komplexeren Subsystem mit einem eigenen Data Link Protokoll.
Das Transmission Control Protocol (TCP)
Das Transmission Control Protocol (TCP) stellt einen zuverlässigen Bytestrom zur Verfügung.
Die Adressierung von Anwendungsprogrammen geschieht durch die IP-Adresse und eine Portnummer. Für Standard-Dienste sind feste Portnummern (well known ports) reserviert. Für eine
Telnet-Verbindung ist dies beispielsweise Port 23, für FTP Port 21 und für X-Window Port 6000.
Portnummern über 1023 sind i.a. frei verfügbar, während die übrigen Portnummern für privilegierte Prozesse reserviert sind.
Der Datentransport findet unter Verwendung einer fensterbasierten Flußkontrolle statt. Man stelle
sich eine Art Fenster vor, das über die zu sendenden Daten hinweg gleitet. Die Daten innerhalb
des Fensters werden byteweise nummeriert gesendet. Sobald eine Bestätigung vom Empfänger
vorliegt, dass das erste Byte angekommen ist, rückt das Fenster eine Stelle weiter. Es unterscheidet
bestätigte von unbestätigten Bytes. Die Breite des Fensters, also die Anzahl der Bytes, die jeweils
gesendet werden und auf deren Bestätigung dann gewartet wird, hängt von der Pufferkapazität
des Empfängers ab. Der Empfänger gibt dabei an, wie viele Daten er annehmen und verarbeiten
kann. Die Datenrate muss also während einer Übertragung nicht unbedingt konstant bleiben. Die
Breite des Fensters lässt sich dynamisch vergrößern oder verkleinern, wenn Ressourcen auf der
Übertragungsstrecke frei werden oder abnehmen.
TCP verwendet eine Voll-Duplex-Verbindung zwischen Sender und Empfänger, d.h. es können über
diese eine Verbindung gleichzeitig in beiden Richtungen Nutzdaten gesendet und Quittungen für
die gesendeten Daten empfangen werden. Anhand dieser Bestätigungen kann der Sender feststellen,
welche Daten der Empfänger erhalten hat und welche unterwegs verloren gegangen sind bzw.
verfälscht wurden und somit nochmal gesendet werden müssen. TCP stellt (im Gegensatz zu
UDP) einen zuverlässigen Dienst zur Verfügung.
In UNIX ist das TCP-Protokoll im Betriebssystemkern implementiert. Benutzt werden TCPDienste über entsprechende Systemaufrufe. Diese Aufrufe sind als Socket-Schnittstelle bekannt
(näheres dazu in Kapitel über Socket-Programmierung).
Das User Datagram Protocol (UDP)
Das Transportprotokoll UDP ist ebenfalls auf das IP-Protokoll aufgesetzt. Im Gegensatz zu TCP
arbeitet es verbindungslos, d.h. es werden keine Zustandsinformationen gehalten und damit auch
5
keine Quittungen für die Datagramme versendet. Aufgrund dessen ist innerhalb von UDP eine
gesicherte Datenübertragung nicht möglich, da UDP nicht feststellen kann, ob und ggf. welche
Datagramme bei der Übertragung verloren gegangen sind bzw. verfälscht wurden. Als Folge muss
sich jede UDP-basierte Anwendung selbst um die korrekte Behandlung von verlorenen Paketen
kümmern. Es ist außerdem durchaus möglich, dass UDP-Pakete nicht in der richtigen Reihenfolge beim Empfänger eintreffen, da aufeinander folgende Pakete über unterschiedliche Wege zum
Empfänger gelangen können.
IP-Netze
Jedes Netzwerk im Internet wird durch eine eindeutige Netzadresse und jeder Rechner (Host) in
seinem Netz durch eine eindeutige Hostadresse adressiert. Die klassenbasierte Betrachtung von
IP-Netzen (Class A, Class B, ...) ist mittlerweile veraltet. IP-Adressen gehören üblicherweise zu
einem Netz, das durch eine Netzadresse und eine Netzmaske definiert wird [10]. Die BroadcastAdresse eines Netzes ist die höchste und damit letzte Adresse eines Netzblockes. Sie kann wie folgt
berechnet werden:
IP-Netz ∨ (¬ Netzmaske) = Broadcast-Adresse
Beispiel für das Netz 10.0.0.0 mit der Netzmaske 255.255.255.128 (oder 10.0.0.0/25):
10.0.0.0 ∨ (¬ 255.255.255.128) = 10.0.0.0 ∨ 0.0.0.127 = 10.0.0.127
Tabelle 2 zeigt die Kurzschreibweise von Netzmasken. Grundidee ist dabei, dass man bei so genannten ’continuous netmasks’, also Netmasken mit ausschließlich Einsen am Anfang und Nullen
am Ende, einfach die Anzahl der Einsen zählen kann.
/0
/1
/2
/3
/4
/5
/6
/7
/8
/9
/10
/11
/12
/13
/14
/15
/16
0.0.0.0
128.0.0.0
192.0.0.0
224.0.0.0
240.0.0.0
248.0.0.0
252.0.0.0
254.0.0.0
255.0.0.0
255.128.0.0
255.192.0.0
255.224.0.0
255.240.0.0
255.248.0.0
255.252.0.0
255.254.0.0
255.255.0.0
/16
/17
/18
/19
/20
/21
/22
/23
/24
/25
/26
/27
/28
/29
/30
/31
/32
255.255.0.0
255.255.128.0
255.255.192.0
255.255.224.0
255.255.240.0
255.255.248.0
255.255.252.0
255.255.254.0
255.255.255.0
255.255.255.128
255.255.255.192
255.255.255.224
255.255.255.240
255.255.255.248
255.255.255.252
255.255.255.254
255.255.255.255
Fig. 2: Netzmasken
Weiterhin besteht die Möglichkeit ein gegebenes Netz in verschiedene Subnetze zu unterteilen, um
z.B. das Routing innerhalb großer Netze zu vereinfachen. Dabei ist zu beachten, dass sich die
einzelnen Subnetze nicht überlappen. Die erste Adresse (Netzwerkadresse) und die letzte Adresse
(Broadcastadresse) von Netzen sollten dabei für Rechner verwendet werden.
Literaturhinweise
Die TCP/IP-Protokollfamilie wird sehr ausführlich in den Büchern von D.E. Comer beschrieben.
Die Grundlagen findet man in [2]. Eine gute Einführung in die Materie bieten [1] und [12]. Ein
6
Buch, das sich sehr ausführlich mit der Programmierung von Netzwerken unter UNIX befasst, ist
[13].
Wer es ganz genau wissen will, dem seien die RFC-Dokumente empfohlen. Das IP-Protkoll ist in
[9], TCP in [7] und UDP in [8] beschrieben. In [4] wird IP-Multicasting definiert, [14] beschreibt
das DVMRP. [11] listet alle well-known Ports und reservierten Multicast–Adressen auf. Ansonsten
geben die RFCs [6] und [5] einen Überblick über das Internet und weitere einführende Literatur.
Sockets
Unter dem Betriebssystem UNIX gibt es verschiedene Möglichkeiten zur Interprozesskommunikation (IPC). Eines dieser Konzepte stellen die an der University of California in Berkeley für
das BSD–UNIX entwickelten Sockets (”Steckdose”, abstrakter Kommunikationsendpunkt) dar.
Die Benutzung zeichnet sich durch einfache Funktionsaufrufe aus. Als Preis für dieses primitive
Konzept wird dem Programmierer etwas Mehrarbeit abverlangt.
Wollen mehrere Prozesse miteinander über Sockets kommunizieren, so schreibt ein Prozess, der
Informationen verschicken will, diese in den erzeugten und für diesen Zweck vorgesehenen Socket.
Prozesse, die von dieser Information Gebrauch machen wollen, lesen diese aus dem Socket, der
mit dem sendenden Socket verknüpft ist. Diese Verknüpfung kann auf verschiedene Arten erfolgen. Diese Arten unterscheiden sich in ihren Fähigkeiten, in ihrer Zuverlässigkeit und in ihrer
Verarbeitungsgeschwindigkeit.
Typen von Sockets
Stream Socket (SOCK STREAM): bidirektional und zuverlässig. Daten, die in den Socket hineingeschrieben werden, können an dem anderen Socket mit Sicherheit auch abgeholt werden. Die
Reihenfolge der Daten bleibt unverändert, es gehen allerdings die Datenblockgrenzen verloren (Schreibt ein Prozeß P1 die Datenblöcke D1 und D2 in dieser Reihenfolge in den einen
Socket, so kann Prozeß P2 die Daten auch nur in dieser Reihenfolge abholen, wobei vom
Empfängerprozeß nicht mehr differenziert werden kann, in welchen Portionen die Daten abgesendet wurden, d. h. sie präsentieren sich dem Empfänger als unstrukturierter Bytestrom.
Datagram Socket (SOCK DGRAM): bidirektional und unzuverlässig. Gesendete Datagramme müssen
nicht beim Empfängersocket ankommen, können aber auch mehrfach ankommen und die
Reihenfolge der Datagramme ist nicht garantiert vorhersagbar. Datenblockgrenzen bleiben
erhalten.
Raw Socket (SOCK RAW): wird nur benötigt, um auf die Netzwerkebene direkt zuzugreifen.
Existierende Domänen
Um Sockets benutzen zu können, muss man wissen, in welcher Umgebung (Domäne) man sie
benötigt. Sockets können nicht domänenübergreifend kommunizieren. Es gibt die folgenden Domänen:
unix domain (AF UNIX): besteht aus dem Rechner, auf dem der die Sockets verwaltende Prozess
existiert, d.h. es ist nicht möglich, in dieser Domäne mit einem anderen Rechner zu kommunizieren.
internet domain (AF INET): besteht aus dem gesamten Internet (weltweit).
Für das Praktikum sind die Domäne AF INET und die Socket Typen SOCK STREAM oder SOCK DGRAM
relevant.
7
Benutzung von Sockets
Im folgenden wird erläutert, wie Sockets erzeugt und benutzt werden können. Es ist notwendig
folgende header–files mit #include in das Programm einzubinden:
#include <sys/types.h>
#include <sys/socket.h>
Für die UNIX–Domäne wird zusätzlich
#include <sys/un.h>
benötigt, und für die Internet–Domäne:
#include <netinet/in.h>
#include <netdb.h>
Transportadressen werden allgemein in Strukturen vom Typ struct sockaddr gespeichert, der in
socket.h definiert ist. Für die Internet–Domäne, die uns im Praktikum interessiert, werden die
Adressen in Strukturen vom Typ struct sockaddr in gespeichert, die in /usr/include/netinet/in.h
definiert ist. Bei der Übergabe an Socket–Funktionen müssen die Zeiger auf diesen Typ zu Zeigern
auf die sockaddr Struktur gecastet werden.
Erzeugen von Sockets
Um einen Socket zu erzeugen, wird der folgende Systemaufruf benutzt:
s = socket(domain, type, protocol);
domain:
AF UNIX oder AF INET.
type:
SOCK STREAM oder SOCK DGRAM.
protocol: hier kann explizit ein Protokoll angegeben werden oder 0, um das System zu veranlassen, sich selbst ein passendes Protokoll auszusuchen.
s:
s enthält eine kleine positive ganze Zahl als Filedeskriptor für den erzeugten Socket.
Falls es nicht möglich war, den Socket zu erzeugen, ist s < 0.
Beispiele:
int s;
s = socket(AF_INET, SOCK_STREAM, 0);
s = socket(AF_INET, SOCK_DGRAM, 0);
Namenszuweisung an Sockets
Damit andere Prozesse mit dem soeben erzeugten Socket kommunizieren können, muss unser
Socket einen lokalen Namen (eine Adresse) bekommen. Die Art des Namens ist abhängig von der
für den Socket gewählten Domäne. Dies geschieht mittels folgendem Funktionsaufruf:
result = bind(s, name, namelen);
s:
name:
namelen:
result:
Socket handle.
Zeiger auf eine sockaddr in Struktur.
Größe des Namens in Bytes.
Im Fehlerfall -1, ansonsten 0.
Zur genauen Funktionsweise konsultiere man die Manualpage bind(3n). Den Namen eines Sockets
kann man über folgende Funktion erfragen:
result = getsockname(s, name, namelen);
s:
Socket handle.
8
name:
Zeiger auf eine sockaddr in Struktur, die von der Funktion ausgefüllt wird.
namelen: Zeiger auf einen Integer-Wert, welcher die Größe des Namens angibt Die Funktion setzt
dort die aktuelle Länge des Namens ein.
result: Im Fehlerfall -1, ansonsten 0.
In der Internet–Domäne besteht der Name aus einer 4 Byte langen Rechnernummer und einer 2
Byte langen Portnummer. In der UNIX–Domäne ist ein Socketname ein Dateiname.
Kommunikation zwischen Sockets
Es wird zwischen zuverlässiger und unzuverlässiger Kommunikation unterschieden. Die zuverlässige Kommunikation (TCP) arbeitet verbindungsorientiert, die unzuverlässige Kommunikation (UDP)
dagegen verbindungslos.
Verbindungsaufbau zwischen Sockets
Das weit verbreitete Client-Server–Konzept kommt auch bei der Socket-Benutzung zum Tragen.
Der Server erzeugt und benennt Sockets, und die Clients verbinden ihrerseits ihre eigenen Sockets
mit den Server-Sockets (wobei eine Verbindung zwischen genau zwei Sockets besteht).
Vom Client wird also der nachfolgende Aufruf benutzt, um sich mit einem Server Socket zu verbinden:
result = connect(s, name, namelen);
Filedeskriptor eines neu erzeugten Socket. Wurde noch kein bind() ausgeführt, so weist
connect() dem Socket automatisch eine lokale Adresse zu.
name:
Zeiger auf eine Struktur sockaddr in, in der die Adresse des Server-Sockets enthalten
ist.
namelen: Größe der Server-Adresse in Bytes.
result: Ist < 0, falls beim Verbindungsaufbau ein Fehler aufgetreten ist; die genauen möglichen
Fehlermeldungen sind in der Manualpage connect(3n) beschrieben.
s:
Bevor sich jedoch Clients an einen Server-Socket anmelden können, muss der Server–Prozess einen
Socket dafür vorbereiten und anschließend auf Verbindungswünsche warten:
result = listen(s, n);
s:
n:
result:
Filedescriptor des mit bind() vorbereiteten Server-Sockets.
Maximale Länge der Warteschlange für einkommende Verbindungswünsche.
Im Fehlerfall -1, ansonsten 0.
Anschließend kann der Server den ersten Client in der Warteschlange bedienen. Dies geschieht
folgendermaßen:
newsocket = accept(s, name, namelen);
Filedeskriptor des mit listen() vorbereiteten Sockets.
Zeiger auf eine Struktur sockaddr in, in der die Funktion die Adresse des Clienten
einträgt.
namelen:
Zeiger auf einen Integer, welcher die Größe von name angibt. Die Funktion trägt die
Länge der Client-Adresse ein.
newsocket: Neuer Socket über den mit dem Client kommuniziert werden kann.
s:
name:
Jetzt können sowohl Client als auch Server in den ihnen gehörenden Socket schreiben und auch
aus ihm lesen. Die Aufrufe dazu lauten:
result = write(s, data, length);
s:
Socket handle
9
data:
Zu sendender Datenblock, ein Characterarray (Folge von Bytes).
length: Länge des zu sendenden Datenblocks.
result: Ist < 0, wenn bei dem Funktionsaufruf ein Fehler aufgetreten ist.
result = read(s, buffer, length);
s:
buffer:
length:
result:
Socket handle
Platz für Datenblock
Größe des bereitgestellten Puffers.
Anzahl der gelesenen Bytes. Im Fehlerfall < 0. Der Wert 0 bedeutet ”End of File”.
Verbindungslose Sockets
Soll ein Server mehrere Clients gleichzeitig bedienen, ohne mehrere Sockets zu erzeugen, so kann
man einen verbindungslosen Socket des Typs SOCK DGRAM benutzen.
Der Client verschickt nun seine Datenblöcke an den Server mit folgender Funktion:
result = sendto(s, msg, len, flags, to, tolen);
Filedescriptor des Client-Socket. Wurde dem Socket noch keine Adresse zugewiesen (bind()),
so macht sendto() das automatisch.
msg:
Datenblock (Characterarray)
len:
Größe des Datenblocks in Bytes.
flags: siehe Manualpage sendto(3n), meist einfach 0.
to:
Zeiger auf eine sockaddr in Struktur mit der Adresse des Server-Sockets
tolen: Größe der Serveradresse in Bytes
result: Die Anzahl der gesendeten Bytes. Im Fehlerfall −1.
s:
Der Server muss seinem Socket mit bind() einen Namen zuweisen, unter dem die Clients ihn
ansprechen können. Dann kann er die ankommenden Daten einfach aus dem Socket lesen:
result = read(s, buffer, length);
s:
buffer:
length:
result:
Filedescriptor des Server-Socket
Platz für Datenblock
Größe des bereitgestellten Puffers.
Anzahl der gelesenen Bytes. Im Fehlerfall < 0. Der Wert 0 bedeutet ”End of File”.
Alternativ kann jedoch auch folgender Aufruf verwendet werden, welcher zusätzlich noch den
Absender des Datenblocks ermittelt:
result = recvfrom(s, buf, len, flags, from, fromlen);
s:
buf:
len:
flags:
from:
fromlen:
result:
Filedescriptor des Server-Socket
Platz für Datenblock
Größe des bereitgestellten Puffers.
siehe Manualpage recvfrom(3n), meist einfach 0.
Zeiger auf eine (leere) sockaddr in Struktur; wird von recvfrom() ausgefüllt
Zeiger auf einen Integer-Wert, welcher die Größe von from angibt; wird von recvfrom()
ausgefüllt.
Anzahl der gelesenen Bytes. Im Fehlerfall < 0. Der Wert 0 bedeutet ”End of File”.
Input/Output-Multiplexing
Mit den bisher beschriebenen Systemaufrufen ist es schwierig, Programme zu schreiben, die über
mehrere Sockets ”gleichzeitig” mit anderen Prozessen kommunizieren. Unter BSD–UNIX ist daher
der Systemaufruf select() definiert. Mit diesem kann man eine Menge von File-Deskriptoren
(nicht nur Sockets) gleichzeitig daraufhin prüfen, ob Verbindungs-, Lese- oder Schreibwünsche für
10
diese vorliegen oder ein sonstiges Ereignis aufgetreten ist. Darüberhinaus kann man optional einen
Timeout–Wert angeben: Spätestens nach Ablauf dieser Zeit kehrt der Systemaufruf zurück, auch
wenn bis dahin keine I/O–Operationen bemerkt worden sind.
Die genaue Beschreibung von select(3c) findet sich in der Manualpage. An dieser Stelle ist
sicherlich ein kleines Programmfragment als Beispiel nützlicher. Angenommen, ein Programm benutzt sowohl verbindungsorienterte als auch verbindungslose Kommunikation. Dazu hat es die
beiden Sockets tcpsock und udpsock eingerichtet und möchte nun darauf warten, daß Verbindungswünsche bzw. Daten eingehen. Ist 10 Sekunden lang nichts eingetroffen, geht das Programm
davon aus, daß irgendetwas nicht stimmt und möchte entsprechende Aktionen auslösen:
fd_set
int
struct timeval
readfds;
/* Menge von Filedeskriptoren */
dtbsz = getdtablesize(); /* Groesse der Menge */
to;
/* Struktur fuer Timeout-Zeit */
...
FD_ZERO (&readfds);
FD_SET (tcpsock, &readfds);
FD_SET (udpsock, &readfds);
/* readfds := leere Menge */
/* tcpsock hinzufuegen
*/
/* udpsock hinzufuegen
*/
to.tv_sec = 10;
to.tv_usec = 0;
/* Timeout nach 10 sec
switch (select (dtbsz, &readfds, (fd_set *) 0,
(fd_set *) 0, &to))
{
case -1:
if (errno == EINTR)
{
/* select() wurde unterbrochen; im Normalfall einfach
/* noch einmal versuchen...
...
}
/* sonstiger Fehler! Jetzt wird’s ernst. */
perror ("select");
...
break;
case 0:
/* Timeout! Die 10 sec sind abgelaufen! */
...
break;
default:
if (FD_ISSET (tcpsock, &readfds))
{
/* Es hat sich jemand am TCP-Socket gemeldet! */
newsock = accept (tcpsock, ... );
...
}
if (FD_ISSET (udpsock, &readfds))
{
/* Am UDP-Socket sind Daten angekommen! */
len = recvfrom (udpsock, ... );
...
}
*/
*/
*/
11
}
In den meisten Fällen wird so ein select() in eine Schleife eingebaut sein.
Löschen von Sockets
Wenn man ein Socket nicht mehr benötigt, sollte man ihn aus dem System entfernen. Dazu dient
der Systemaufruf close():
result = close(s);
s:
Socket Handle.
result: Im Fehlerfall -1, ansonsten 0.
Eine zusätzliche Möglichkeit, eine Verbindung in einer Richtung oder beiden Richtungen zu beenden, ist der Funktionsaufruf:
result = shutdown(s, how);
Socket handle.
0: will nicht mehr lesen.
1: will nicht mehr schreiben.
2: will gar nichts mehr.
result: Im Fehlerfall -1, ansonsten 0.
s:
how:
Literaturhinweise
Das Thema Netzwerkprogrammierung wird sehr ausführlich in [13] behandelt. Eine gute Einführung
mit Programmbeispielen findet man in [3], Kapitel 7 und 8. Die Manpages zum Thema Sockets sind
accept(3n), bind(3n), close(2), connect(3n), getsockname(3n), getpeername(3n),
listen(3n), read(2), recvfrom(3n), select(3c), sendto(3n), setsockopt(3n),
shutdown(3n), socket(3n), socketpair(3n), write(2).
Literatur
[1] T. Braun and M. Zitterbart. Hochleistungskommunikation; Band 2: Transportdienste und
-protokolle. Oldenbourg, München, 1996.
[2] D.E. Comer. Internetworking with TCP/IP Volume 1: Principles, Protocols, and Architecture.
Prentice Hall, 2 edition, 1991.
[3] CSRG, University of California. Unix Programmer’s Manual - Supplementary Documents 1,
April 1986.
[4] S. Deering. Host Extensions for IP Multicasting. RFC 1112, Stanford University, August
1989.
[5] E. Hoffman and L. Jackson. FYI on Introducing the Internet – A Short Bibliography of
Introductory Internetworking Reading for the Network Novice. RFC 1463, Merit Network
Inc., NASA, May 1993.
[6] E. Krol and E. Hoffman. FYI on “What is the Internet?”. RFC 1462, University of Illinois,
Merit Network Inc., May 1993.
[7] J. Postel. TRANSMISSION CONTROL PROTOCOL. RFC 793, ISI, September 1981.
[8] Jon Postel. User Datagram Protocol. RFC 768, ISI, August 1980.
12
[9] Jon Postel. INTERNET PROTOCOL. RFC 791, ISI, September 1981.
[10] Y. Rekhter and T. Li. An Architecture for IP Address Allocation with CIDR. RFC 1518,
September 1993.
[11] J. Reynolds and J. Postel. ASSIGNED NUMBERS. RFC 1700, ISI, October 1994.
[12] M. Santifaller. TCP/IP und ONC/NFS in Theorie und Praxis. Addison Wesley, 1995.
[13] W.R. Stevens. UNIX Network Programming. Prentice Hall, 1992.
[14] D. Waitzman, C. Partridge, and S. Deering. Distance Vector Multicast Routing Protocol.
RFC 1075, BBN STC, Stanford University, November 1988.
Herunterladen