Netzwerk Programmierung ist ein Thema, dass mich fasziniert, seit ich mein erstes Modem 1989 gekauft habe. Aus dieser Faszination entstand mein eigenes Mailboxprogrammauf dem Commodore Amiga, dass mit dem Z-Netz kommunizieren konnte. Die Faszination habe ich ins Internet-Zeitalter mitgenommen. Hier will ich dir vermitteln, wie du in Python Daten per TCP/IP austauschen kannst.
Netzwerk im Internet
Das hauptsächlich benutzte Protokoll im Internet ist das Transmission Control Protocol (TCP), um mir hier langwierige Erklärungen zu ersparen, verweise ich dich auf den Artikel in der Wikipedia.
Request / Response Mechanismus

Üblicherweise stellt ein Client einen Request an den Server, der daraufhin mit einem Response antwortet. Beispielsweise ist beim Web-Browser der Request, eine bestimmte Datei zu schicken, der Web-Server packt entweder den Inhalt der Datei in den Request oder eine Fehlermeldung, falls diese nicht verfügbar ist.
Üblicherweise wird der Server nicht einen Request verarbeiten, sondern eine große Menge Anforderungen vearbeiten, weswegen der Servercode üblicherweise im Multithreading programmiert wird.
Netzwerk ZUgriff mit dem urllib Package
Zunächst will ich dir zeigen, wie einfach die index.html dieser Website mit Python runterladen kannst. Dazu gibt es das urllib
Package
# encoding: UTF-8
import urllib.request
# URL öffnen und Daten anfordern. Ohne spezielle Dateiangabe
# liefert der Webserver immer die index.html zurück.
fp = urllib.request.urlopen('https://raspithek.de')
# Daten auslesen
data = fp.read()
# Die Daten sin vom Datentyp Bytes und müssen daher erst
# zum UTF-8 String umkodiert werden.
str = data.decode('utf8')
fp.close()
print(str)
Die Methode urlopen()
setzt den Request ab und wartet auf den Response.
Netzwerk ZUgriff mit dem request Package
Wie so oft gibt es ein Package, dass uns die Arbeit noch etwas einfacher macht. Mit dem requests Package entfällt das umkodieren des Response-Inhalts zu einem String.
import requests
url = requests.get('https://raspithek.de')
data = url.text
print(data)
Fibonacci Server
Als Beispiel für ein Client/Server-Modell, wollen wir im folgenden einen Fibonacci Server an den Start bringen. Der Server nimmt eine Integer Zahl entgegen und berechnet die dazugehörige Zahl aus der Fibonacci Folge und sendet das Ergebnis an den Client zurück. Dazu benötigen wir erstmal etwas Theorie
Die Fibonacci Folge
Die Fibonacci Folge wurde vom Mathematiker Leonardo da Pisa (auch Fibonacci genannt) im 13. Jhdt. beschrieben, um das Wachstum einer Kaninchen Population zu beschreiben. Ein Element der Folge berechnet sich aus der Summe seiner beiden Vorgänger in der Folge oder mathematisch ausgedrückt f(n)= fn(n-1) + f(n-2), wobei f(0) = 0 und f(1) = 1 definiert sind.
Zum besseren Verständnis liste ich die ersten zehn Elemente der Fibonacci Folge hier mal auf.
Listenelement | Wert |
---|---|
0 | 0 |
1 | 1 |
2 | 1 |
3 | 2 |
4 | 3 |
5 | 5 |
6 | 8 |
7 | 13 |
8 | 21 |
9 | 34 |
Als Profi hast du vermutlich selbst gerade ausgerechnet, dass das f(10) = 55 ist.
Einschub Rekursion
Die Fibonacci Folge ist das klassische Beispiel für eine rekursive Funktion. Das ist eine Funktion, die sich selber mit anderen Parameterwerten wieder aufruft.
Zu einer rekursiven Methode gehören immer der Rekursionsaufruf und die Rekursionsbedingung.
Der Rekursionsaruf sorgt, wie der Name sagt, dass sich die Funktion selbst wieder aufruft.
Die Rekursionsbedingung dient dazu, die Rekursion an geeigneter Stelle zu beenden, da der Aufruf sonst unendlich weiterlaufen würde.
#fib.py
import sys
# berechnet rekursiv die Fibonaccizahl n.
def fib(n):
# Rekursionsbedingung
if n in [0,1]:
return 1
else:
# Rekursionsaufruf
return fib(n-1) + fib(n-2)
element = int(sys.argv[1])
print(fib(element))
Ich habe hier eine Neuerung eingebaut, nähmlich das Auslesen eines über die Kommandozeile übergeben Parameters an das Programm. Du kannst mit dem Aufruf
python fib.py 6
von aussen steuern, welches Element der Fibonacci Reihe berechnet werden soll. Der erste übergebene Parameter wird dem Programm in sys.arg[1]
mitgeliefert. Der zweite in arg[2]
, der dritte in arg[3]
usw. In argv[0]
steht übrigens der Dateiname des Programm selbst.
Der Parameter wird dann als Integer in die Methode fib()
gesteckt, die sich mit
fib(n-1) + fib(n-2)
zweimal selber rekursiv aufruft, das ist der Rekursionsaufruf (der hier sogar zweimal passiert).
Die Rekursionsbedingung zum Beenden der Rekursion passiert mit
if n in [0,1]:
return 1
Da fib(0 und fib(1) jeweils als 1 definiert sind.
Protokolle, Ports und Sockets
Ein Socket ist ein Objekt, dass uns Python zur Verfügung stellt, um über einen Port Daten zu senden und zu empfangen.

Jetzt geht’s endlich ans Netzwerk. Du kennst natürlich einige Protokolle im Internet wie bspw. http, ssh und ftp. Jedem Protokoll ist ein Port zugeordnet (http:80, ssh 22, ftp, 21). Der Port dient dazu, auf dem Zielcomputer den ankommenden Request dem richtigen Programm zuzuordnen, da ein Computer durchaus mehrere Services anbieten kann.
Der Fibonacci Server
Unser Testserver soll das gleiche machen, wie das obige Programm, mit dem Unterschied, dass der Kommdozeilenparameter übers Netzwerk empfangen werden soll und das Ergebnis auf gleichem Weg zurückgeschickt wird. Zunächst brauchen wir einige Konstanten. Da diese für Server und Client identisch sind, habe ich sie in eine Datei const.py
im Modul network
ausgelagert.
import socket
# TCP Port
__PORT__ = 6554
#Server-IP
# für den Server (immer localhost)
__SERVERLOCAL__ = socket.gethostbyname(socket.gethostname())
#für Server auf einem Remote-Pi
__SERVERREMOTE__ = socket.gethostbyname('gabbo')
# Server-Adresse (IP,Port)
#lokal
__ADDRESSLOCAL__ = (__SERVERLOCAL__,__PORT__)
#remote
__ADDRESSREMOTE__ = (__SERVERREMOTE__,__PORT__)
Mit der Methode socket.gethostbyname()
wird die IP des Zielservers ermittelt und damit zusammen mit dem __PORT__ ein Tupel mit der Adresse des Servers gebildet. Da Client und Server in diesem Beispiel auf einem Raspberry Pi laufen sollen, kannst du den Hostname mit socket.gethostname()
auslesen. Soll der Server auf einem anderen Raspi laufen, benutzt du __ADRESSREMOTE__ und trägst bei __SERVERREMOTE__ den Hostname deine Raspberry Pi ein..
Nun kommen wir zum eigentlichen Servercode.
#! /usr/bin/python3
import socket
import threading
from network.const import __ADDRESS__
# Server-Socket aufbauen
server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#Socket an unsere Server-Adresse binden
server.bind(__ADDRESS__)
# Set mit den verbundenen Clients
clients = set()
# Locks für den Zugriff auf das Set
clients_lock = threading.Lock()
# Unsere Arbeitsmethode: rekursive Fibonacci-Reihe
def fib(n):
# Rekursionsbedingung
if n in [0,1]:
return 1
else:
# Rekursionsaufruf
return fib(n-1) + fib(n-2)
#bearbeitet eine eingehende Connection annehmen, pro Client ein seperater Thread
def handle_connection(conn,addr):
print(f"[NEW CONNECTION] {addr} Connected")
try:
connected = True
while connected:
msg = conn.recv(64).decode('utf-8')
print(f'[RECEIVED] {msg}')
if not msg:
break
else:
if msg.startswith('fib:'):
element = int(msg[4:])
fibo = fib(element)
print(f'[SENDING] fib({element})={fibo}')
# Ergebnis an Client senden.
with clients_lock:
for c in clients:
c.sendall(f'{fibo}'.encode('utf-8'))
else:
pass
if msg == 'quit':
print('[DISCONNECT]')
connected = False
with clients_lock:
for c in clients:
c.sendall(f"[{addr}] {msg}".encode('utf-8'))
finally:
# Clientverbindung abbauen und aus Set entfernen.
with clients_lock:
clients.remove(conn)
conn.close()
# Start des Servers
def start():
print('[SERVER STARTED]!')
# Horchen nach eingehender Verbindung
server.listen()
while True:
# Neue Verbindung annehmen
conn, addr = server.accept()
# Neuen Client in Set aufnehmen
with clients_lock:
print(f'[ADDING CONNECTION] {conn}')
clients.add(conn)
#Thread zur Bearbeitung des Requests starten.
thread = threading.Thread(target=handle_connection, args=(conn, addr))
thread.start()
#Hauptprogramm: Serverstart einleiten
if __name__ == '__main__':
start()
Ich habe zwei neue Dinge eingebaut, zum einen den „Shebang“ in der ersten Zeile (ja, das heißt wirklich so). Dies erspart dir dir Angabe des Python-Interpreters zum Programmstart. Wenn du mit
chmod u+x fibserver.py
Das Skript als Executable markierst, findet die Shell den Interpreter über den Shebang und führt diesen mit Angabe des Pythoncodes aus. Du kannst den Server einfach mit
./fibserver.py
starten. Die andere Neuheit ist dieser Codeteil
#Hauptprogramm: Serverstart einleiten
if __name__ == '__main__':
start()
Dies ist eine Konvention, die im Hauptprogramm auftauchen sollte. Andere Programmierer, die das sehen, wissen dann, dass diese Datei das Hauptprogramm enthält.
Ansonsten macht das Serverprogramm nicht anderes, als auf Port 6554 auf eine eingehende Verbindung zu warten. Sobald ein Client diese aufbaut, wird in einem neuen Thread der Request bearbeitet und die angeforderte Fibonaccizahl berechnet. Ich habe mich hier für ein einfaches Protokoll entschieden. Der Request muss lediglich den String fib:<element> enthalten.
Ein Request, der nicht mit fib: beginnt wird einfach ignoriert, sonsten wird mittels
element = int(msg[4:])
Die Elementnummer aus dem Request isoliert. Nach der Berechnung der Fibonaccizahl wird diese einfach über die sendall() Methode an alle verbundenen Clients zurückgesendet (das ist etwas praxisfremd und soll nur nochmal den Locking-Mechanismus für Threads demonstrieren). Wenn allerdings im Request der String ‚quit‘ gesendet wird, wird der Thread für diesen Client beendet und die dazugehörige Verbindung abgebaut.
Der Fibonacci Client
Der Fibonacci Client hingegen hat folgende Aufgabe: Er baut eine Verbindung zum Server auf und nimmt dann eine Eingabe über die Tastatur entgegen. Diese wird dann direkt an den Server geschickt oder mit ‚q‘ die Verbindung abgebaut.
Test
Um die Kommunikation zwischen Client und Server zu testen, musst du zwei ssh-Verbindungen aufmachen. In der einen startest du den Server
./fibserver.py
Im anderen den Client
python fibclient.py
Ich hab den Server mal auf dem Raspberry Pi aus dem Homserver-Projekt gestartet.Ich stell hier mal die Ausgaben des Servers und des Clients untereinander:


Das war’s zum Thema Netzwerkprogrammierung, im nächsten Teil will ich dir das Logging etwas näher bringen.