1.0
Dieser Foliensatz basiert lose auf Folien von Prof. Dr. Henning Pagnia.
Alle Fehler sind meine eigenen.
Die Vermittlungsschicht (Internet Layer)
übernimmt das Routing
realisiert Ende-zu-Ende-Kommunikation
überträgt Pakete
ist im Internet durch IP realisiert
löst folgende Probleme:
Sender und Empfänger erhalten netzweit eindeutige Bezeichner (⇒ IP-Adressen)
die Pakete werden durch spezielle Geräte (⇒ Router) weitergeleitet
Transmission Control Protocol (TCP), RFC 793
verbindungsorientierte Kommunikation
ebenfalls Konzept der Ports
Verbindungsaufbau zwischen zwei Prozessen (Dreifacher Handshake, Full-Duplex-Kommunikation)
geordnete Kommunikation
zuverlässige Kommunikation
Flusskontrolle
hoher Overhead ⇒ eher langsam
nur Unicasts
User Datagram Protocol (UDP), RFC 768
verbindungslose Kommunikation via Datagramme
unzuverlässig (⇒ keine Fehlerkontrolle)
ungeordnet (⇒ beliebige Reihenfolge)
wenig Overhead (⇒ schnell)
Größe der Nutzdaten ist 65507 Byte, in der Praxis jedoch meist deutlich kleiner
Anwendungsfelder:
Anwendungen mit vorwiegend kurzen Nachrichten (z. B. NTP, RPC, NIS)
Anwendungen mit hohem Durchsatz, die ab und zu Fehler tolerieren (z. B. Multimedia)
Multicasts sowie Broadcasts
RFC 7230 – 7235: HTTP/1.1 (redigiert im Jahr 2014; urspr. 1999 RFC 2626)
RFC 7540: HTTP/2 (seit Mai 2015 standardisiert)
Eigenschaften:
Client / Server (Browser / Web-Server)
basierend auf TCP, i. d. R. Port 80
Server (meist) zustandslos
seit HTTP/1.1 auch persistente Verbindungen und Pipelining
abgesicherte Übertragung (Verschlüsselung) möglich mittels Secure Socket Layer (SSL) bzw. Transport Layer Security (TLS)
HTTP-Kommandos
(„Verben“)
HEAD
GET
POST
PUT
PATCH
DELETE
OPTIONS
TRACE
CONNECT
...
Aufbau der Dokumentenbezeichner Uniform Resource Locator (URL)
scheme://host[:port][abs_path[?query][#anchor]]
Protokoll (case-insensitive) (z. B. http, https oder ftp)
DNS-Name (oder IP-Adresse) des Servers (case-insensitive)
(optional) falls leer, 80 bei http und 443 bei https
(optional) Pfadausdruck relativ zum Server-Root (case-sensitive)
(optional) direkte Parameterübergabe (case-sensitive) (?from=…&to=…)
(optional) Sprungmarke innerhalb des Dokuments
Uniform Resource Identifier (URI) sind eine Verallgemeinerung von URLs.
definiert in RFC 1630 (im Jahr 1994)
entweder URL (Location) oder URN (Name) (z. B. urn:isbn:1234567890)
Beispiele von URIs, die keine URL sind, sind XML Namespace Iidentifiers
<svg version="1.1" xmlns="http://www.w3.org/2000/svg">...</svg>
Dient dem Anfordern von HTML-Daten vom Server (Request-Methode).
Minimale Anfrage:
GET <Path> HTTP/1.1 Host: <Hostname> Connection: close <Leerzeile (CRLF)>
Client kann zusätzlich weitere Infos über die Anfrage sowie sich selbst senden.
Server sendet Status der Anfrage sowie Infos über sich selbst und ggf. die angeforderte HTML-Datei.
Fehlermeldungen werden ggf. vom Server ebenfalls als HTML-Daten verpackt und als Antwort gesendet.
Beispiel Anfrage des Clients
GET /web/web.php HTTP/1.1
Host: archive.org
**CRLF**
Beispiel Antwort des Servers
HTTP/1.1 200 OK
Server: nginx/1.25.1
Date: Thu, 22 Feb 2024 19:47:11 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
**CRLF**
<!DOCTYPE html>
…
</html>**CRLF**
Sockets sind Kommunikationsendpunkte.
Sockets werden adressiert über die IP-Adresse (InetAddress-Objekt) und eine interne Port-Nummer (int-Wert).
Sockets gibt es bei TCP und auch bei UDP, allerdings mit unterschiedlichen Eigenschaften:
verbindungsorientierte Kommunikation über Streams
verbindungslose Kommunikation mittels Datagrams
Das Empfangen von Daten ist in jedem Fall blockierend, d. h. der empfangende Thread bzw. Prozess wartet, falls keine Daten vorliegen.
Der Server-Prozess wartet an dem bekannten Server-Port.
Der Client-Prozess erzeugt einen privaten Socket.
Der Socket baut zum Server-Prozess eine Verbindung auf – falls der Server die Verbindung akzeptiert.
Die Kommunikation erfolgt Strom-orientiert: Für beide Parteien wird je ein Eingabestrom und ein Ausgabestrom eingerichtet, über den nun Daten ausgetauscht werden können.
Wenn alle Daten ausgetauscht wurden, schließen im Allg. beide Parteien die Verbindung.
import sys
import socket
def scan_port(host, port):
try:
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(0.5) # Set a timeout to avoid hanging connections
s.connect((host, port))
print(f"Port {port} is open on {host}")
except (ConnectionRefusedError, TimeoutError) as e:
pass # Port is likely closed, expected behavior
def main():
host = "localhost"
if len(sys.argv) > 1: host = sys.argv[1]
for port in range(1, 1024): scan_port(host, port)
if __name__ == "__main__":
main()
Nach erfolgtem Verbindungsaufbau können zwischen Client und Server mittels sendall und recv Daten ausgetauscht werden.
Wir können blockierend auf Daten warten bzw. blockierend schreiben, indem wir recv bzw. sendall aufrufen. (Siehe nächstes Beispiel.)
Sollte die Verbindung abbrechen oder die Gegenseite nicht antworten, kann es „relativ lange dauern“, bis dieser Fehler erkannt bzw. gemeldet wird.
Wir können den Socket auch in den nicht-blockierenden Modus versetzen, indem wir setblocking(False) aufrufen (ggf. sinnvoll).
# Client
import socket
def receive_all(conn, chunk_size=1024):
data = b''
while True:
part = conn.recv(chunk_size)
data += part
if len(part) == 0: break # no more data
return data
while True:
the_line = input()
if the_line == ".": break
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect(("localhost", 5678)) # Connect to localhost on port 5678
s.sendall(the_line.encode())
data = receive_all(s)
print(data.decode())
# Server
import socket
def receive_all(conn, chunk_size=1024): # see previous example
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as server:
server.bind(("localhost", 5678)) # Bind to localhost on port 5678
server.listen(1) # queue at most one connection at a time
while True:
conn, addr = server.accept()
with conn:
print(f"Connection from {addr}.")
data = receive_all(conn, 1024)
print(f"Received {data}.")
if data:
conn.sendall(data)
Python erlaubt es Sockets zu Wrappen, um sie wie Dateien behandeln zu können.
<Socket>.makefile(mode="r?w?b?" [, encoding="utf-8"]) erzeugt ein Dateiobjekt, das (insbesondere) readline() und write() unterstützt. Dies kann insbesondere bei zeilenorientierter Kommunikation hilfreich sein.
Es können auch ganze Dateien über Sockets basierend übertragen werden (<Socket>.sendfile(<File>)).
Einige Methoden sind nur auf spezifischen Betriebssystemen (meist Unix) verfügbar.
Clientseitig
Datagram-Socket erzeugen und an Zieladresse binden
Nachricht erzeugen (ggf. vorher maximale Länge prüfen)
Datagram absenden
ggf. Antwort empfangen und verarbeiten
Serverseitig
Datagram-Socket auf festem Port erzeugen
(Die Hostangabe bestimmt wer sich mit dem Socket verbinden darf; localhost bedeutet nur lokale Verbindungen sind erlaubt.)
Endlosschleife beginnen
Datagram empfangen (und verarbeiten)
ggf. Antwort erstellen und absenden
import socket
HOST = "localhost"
PORT = 5678
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as server:
server.bind((HOST, PORT))
while True:
data, addr = server.recvfrom(65507) # buffer size is 65507 bytes
print(f"received message: {data} from: {addr}")
server.sendto(data, addr)
Ein einfacher HTTP-Client
Schreiben Sie einen HTTP-Client, der den Server www.michael-eichberg.de kontaktiert, die Datei /index.html anfordert und die Antwort des Servers auf dem Bildschirm ausgibt.
Verwenden Sie HTTP/1.1 und eine Struktur ähnlich dem in der Vorlesung vorgestellten Echo-Client.
Senden Sie das GET-Kommando, die Host-Zeile sowie eine Leerzeile als Strings an den Server.
Erweitern Sie Ihren Client um die Fähigkeit URLs auf der Kommandozeile anzugeben.
Verwenden Sie existierende Funktionalität, um die angegebene URL zu zerlegen (urlparse von urllib.parse).
Speichern Sie die Antwort des Servers in einer lokalen Datei. Prüfen Sie, dass die Datei in einem Browser korrekt angezeigt wird.
Kann Ihr Programm auch Bilddateien (z. B. "http://www.michael-eichberg.de/acm.svg") korrekt speichern? Falls nicht, prüfen Sie ob Sie Antwort des Servers richtig verarbeiten; analysieren Sie ggf. den Header und passen Sie Ihr Programm entsprechend an.
Protokollaggregation
Schreiben Sie einen Python basierten Server und Client, mit dem sich Protokoll-Meldungen auf einem Server zentral anzeigen lassen. Das Programm soll mehrere Clients unterstützen und UDP verwenden. Jeder Client liest von der Tastatur eine Eingabezeile in Form eines Strings ein, validiert die Eingabe und sendet diese dann ggf. sofort zum Server. Der Server wartet auf Port 5678 und empfängt die Meldungen beliebiger Clients, die er dann unmittelbar ausgibt.
Stellen Sie sicher, dass Fehler adäquat behandelt werden.
Sie können den UDP basierten Echo Server als Vorlage für Ihren Server verwenden.