i

Fachkonzept - Client-Server-Kommunikation mit Sockets

Bevor du diese Seite liest, solltest du das Fachkonzept - Client-Server-System kennen.

Die folgende Abbildung verdeutlicht die Aktionen von Client und Server bei einer Client-Server-Kommunikation:

Client-Server-Kommunikation

Der Server muss hierzu zunächst einen sog. Verbindungssocket erzeugen und diesen Socket an IP-Adresse und Port eines Prozesses binden. Abschließend beginnt der Socket mit dem Lauschen auf Verbindungsanfragen.

Ein Client muss ebenfalls einen Socket erzeugen, der hier Kommunikationsssocket genannt wird. Anschließend kann ein solcher Kommunikationssocket mit dem Verbindungssocket des Servers Kontakt aufnehmen und einen Verbindungswunsch anfragen. Hier läuft (unter TCP) dann ein Drei-Wege-Handschlagprotokoll ab.

Wenn die Verbindsanfrage vom Verbindungssocket des Servers akzeptiert wird, dann erzeugt der Verbindungssocket einen zusätzlichen Kommunikationssocket. Der Verbindungssocket übernimmt also nicht den Austausch der Daten, sondern überlässt das dem neu erzeugten Kommunikationssocket, um selbst auf weitere Verbindungsanfragen reagieren zu können.

Die Kommunikationssockets von Client und Server können jetzt Daten senden und empfangen.

Wenn der Nachrichtenaustausch beendet ist, werden die Kommunikationssockets geschlossen. Der Verbindungssocket des Servers wird in der Regel erst dann geschlossen, wenn der Server beendet wird.

Operationen zur Umesetzung einer Client-Server-Kommunikation in Python

Die Socket-Schnittstelle (Socket-API) legt Operationen fest, um den oben beschriebenen Client-Server-Kommunikation zu realisieren:

Client-Server-Kommunikation

Den Server für die Kommunikation vorbereiten

Wenn ein Client mit einem Server kommunizieren möchte, muss er eine Verbindung bei dem Server anfragen. Um die Verbindung annehmen zu können, muss der Server darauf vorbereitet sein. Dazu benötigt man einen Verbindungssocket. Ein Verbindungssocket wird, genau wie ein Kommunikationssocket, in Python durch ein Software-Objekt dargestellt. In der Regel wird der Verbindungssocket nur ein einziges mal erzeugt und bleibt während der gesamten Laufzeit des Servers bestehen.

Das folgende Beispiel zeigt, wie ein Verbindungssocket erzeugt wird:

import socket

# Verbindungssocket erzeugen
verbindungs_s = socket.socket()

# Verbindungssocket an einen Port "binden".
# Der Server wird also über Port 5000 erreichbar sein.
verbindungs_s.bind(('', 5000))

# mit dem Lauschen beginnen
# (d.h. nach diesem Befehl werden höchstens 20 Verbindungsanfragen
# in eine Warteschlange eingereiht)
verbindungs_s.listen(20)

Der Client fragt eine Verbindung an

Mit der Methode komm_s.connect( (ip_adresse, port) ) kann ein Client eine Verbindung bei einem Server anfragen. Als Parameter erwartet diese Methode ein Tupel, in dem die IP-Adresse und der Port des Servers gespeichert ist:

import socket
komm_s = socket.socket()
adresse = ('127.0.0.1', 5000)
komm_s.connect(adresse)

Der Server nimmt eine Verbindung an

Server, die wir programmieren, können zu einem Zeitpunkt immer nur mit einem Client kommunizieren. Falls mehrere Verbindungsanfragen gleichzeitig eingehen, werden sie in eine Warteschlange eingereiht. Die Methode verbindungs_s.accept() entnimmt eine Verbindsanfrage aus der Warteschlange und baut die Verbindung auf. Falls es gerade keine Verbindungsanfrage gibt, wartet dieser Befehl auf eine Anfrage. verbindungs_s.accept() gibt zwei Werte zurück: Einen Kommunikationssocket und die ip-Adresse des Clients, zu dem gerade eine Verbindung aufgebaut wurde. Das folgende Beispiel zeigt, wie man diese beiden Rückgabewerte in je einer Variablen speichert:
komm_s, client_adresse = verbindungs_s.accept()

Mit dem Kommunikationssocket kann anschließend genau so gearbeitet werden, wie mit einem Kommunikationssocket, der auf einem Client erzeugt wurde (vgl. Kapitel Client zu einem Geburtstagsserver).

Man kann auch Server programmieren, die mit mehreren Clients gleichzeitig kommunizieren können. Dazu ist jedoch ein größer Aufwand nötig.

Zeichenketten und Bytes-Objekte

Die Methode komm_s.sendall(datenAlsBytesObjekt) akzeptiert als Parameter ein Bytes-Objekt. Ein Bytes-Objekt verwaltet eine Liste von Bytes (d.h. von Zahlen zwischen 0 und 255). Möchte man eine Zeichenkette senden, muss man diese also zunächst in ein Bytes-Objekt umwandeln:

zeichenkette = 'Hallo'
bytesObjekt = bytes(zeichenkette, 'utf-8')

Die Methode komm_s.recv(1024) liefert also Rückgabewert ebenfalls ein Bytes-Objekt. Dieses Objekt kann man wieder in eine Zeichenkette umwandeln:

bytesObjekt = bytes('Hallo', 'utf-8')
zeichenkette = str(bytesObjekt, 'utf-8')

Anzeigen, dass (im Moment) keine weiteren Daten gesendet werden

Der Geburtstagsserver aus Client zu einem Geburtstagsserver benötigt das Geburtsdatum des Benuters. Deshalb muss der Client zunächst das Geburtsdatum an den Server schicken. Woran erkennt der Server, dass der Client das gesamte Geburtsdatum übertragen wurde? Es gibt mehrere Möglichkeiten, wie man dieses Problem lösen könnte. Bei unseren Programm schicken wir immer das Byte 0 um anzuzeigen, dass wir im Moment keine weiteren Daten schicken möchten:

# Sende das Geburtsdatum '14.3.'
tag = '14.'
tagBytesObj = byte(tag, 'utf-8')
komm_s.sendall(tagBytesObj)

monat = '3.'
monatAlsBytesObj = bytes(monat, 'utf-8')
komm_s.sendall(monatAlsBytesObj)

# Bytes-Objekt erzeugen, das nur das Byte 0 enthält:
trennByte = bytes([0])

# Dem Empfänger anzeigen, 
# dass wir im Moment keine weiteren Daten schicken:
komm_s.sendall(trennByte)

Der Empfänger muss also so lange einzelne Bytes empfangen, bis er das Byte 0 empfängt. Diese Aufgaben erledigen für uns folgende Funktionen, die sich in der Datei socketLib.py befinden:

  • empfangeStr(komm_s)
    • Liest Daten, bis der Sender anzeigt, dass er keine weiteren Daten schicken wird.
    • Parameter komm_s: Socket, von dem gelesen werden soll
    • Rückgabewert: gelesene Daten als String (Kodierung utf-8)
  • sendeStr(komm_s, datenStr)
    • Wandelt den String datenStr in ein Bytes-Objekt um (Kodierung utf-8) und versendet diesen String über den Kommunikationssocket komm_s
    • Parameter komm_s: Socket, über den gesendet werden soll
    • Parameter datenStr: String, der umgewandelt und versendet werden soll
  • sendeTrennByte(komm_s)
    • Sendet das Byte 0. Daran erkennt der Empfänger, das der Sender zunächst keine weiteren Daten sende.
    • Parameter komm_s: Socket, über den gesendet werden soll

Suche

v
10.5.4.5
inf-schule.de/rechnernetze/anwendung/socketprogrammierung/Fachkonzept_Client-Server-Kommunikation_mit_Sockets
inf-schule.de/10.5.4.5

Rückmeldung geben