Aufbau eines blitzschnellen eigenständigen WebSocket-Servers mit `websockets` und ASGI
Lukas Schneider
DevOps Engineer · Leapcell

Einführung in die Echtzeitkommunikation
In der heutigen vernetzten digitalen Landschaft ist Echtzeitkommunikation kein Luxus mehr, sondern eine grundlegende Erwartung. Von kollaborativen Dokumentenbearbeitung und Live-Chat-Anwendungen bis hin zu Finanzhandelsplattformen und der Überwachung von IoT-Geräten ist die Fähigkeit, Informationen sofort und kontinuierlich auszutauschen, von größter Bedeutung. Herkömmliche HTTP-Anforderungs-Antwort-Zyklen sind zwar für viele Anwendungsfälle ausgezeichnet, reichen aber oft nicht aus, wenn eine persistente, latenzarme, bidirektionale Kommunikation erforderlich ist. Hier glänzen WebSockets. Sie bieten einen Full-Duplex-Kommunikationskanal über eine einzige TCP-Verbindung und reduzieren den Overhead im Vergleich zu wiederholtem HTTP-Polling drastisch. Obwohl viele Python-Web-Frameworks eine WebSocket-Integration anbieten, gibt es Szenarien, in denen ein eigenständiger, leistungsstarker WebSocket-Server, der von einem vollwertigen Web-Framework entkoppelt ist, die optimale Lösung darstellt. Dieser Artikel befasst sich damit, wie Sie einen solchen Server mit Pythons leistungsstarker websockets
-Bibliothek und der Asynchronous Server Gateway Interface (ASGI)-Spezifikation erstellen können, um effiziente Echtzeitfunktionen zu erschließen.
Grundlegende Komponenten verstehen
Bevor wir uns mit den Implementierungsdetails befassen, lassen Sie uns die Schlüsseltechnologien klären, die unseren leistungsstarken WebSocket-Server untermauern.
WebSockets: Wie erwähnt, ermöglichen WebSockets die Full-Duplex-Kommunikation über eine einzige TCP-Verbindung. Das bedeutet, dass sowohl der Client als auch der Server gleichzeitig Nachrichten senden und empfangen können, ohne für jeden Austausch neue Verbindungen aufbauen zu müssen. Diese persistente Verbindung reduziert Latenz und Overhead erheblich, was sie ideal für Echtzeitinteraktionen macht.
ASGI (Asynchronous Server Gateway Interface): ASGI ist eine Spezifikation für asynchrone Python-Webserver, Frameworks und Anwendungen. Sie definiert eine Standard-Schnittstelle für die Kommunikation zwischen asynchronen Webservern (wie Uvicorn oder Hypercorn) und asynchronen Python-Webanwendungen. ASGI-Anwendungen sind im Wesentlichen asynchrone Aufrufe, die ein Scope-Dictionary (mit Anforderungsdetails) empfangen und über send
- und receive
-Funktionen Ereignisse senden/empfangen. Diese Standardisierung ermöglicht die Interoperabilität zwischen verschiedenen ASGI-Servern und Frameworks und fördert ein robustes und flexibles Ökosystem.
websockets
-Bibliothek: Dies ist eine fantastische Python-Bibliothek, die eine saubere und leistungsstarke API zum Erstellen von WebSocket-Servern und -Clients bietet. Sie kümmert sich um die Low-Level-WebSocket-Protokolldetails, einschließlich Handshakes, Framing und Fehlerbehandlung, sodass sich Entwickler auf die Anwendungslogik konzentrieren können. Ihre asynchrone Natur passt perfekt zu ASGI und dem asyncio
-Paradigma des modernen Python.
Das Prinzip hinter der gemeinsamen Nutzung dieser Komponenten besteht darin, dass ein ASGI-Server als Einstiegspunkt fungiert und WebSocket-Anfragen an unsere websockets
-Anwendung weiterleitet. Die websockets
-Bibliothek selbst kann auch als eigenständiger Server fungieren, aber die Integration mit einem ASGI-Server wie Uvicorn ermöglicht mehr Flexibilität, insbesondere wenn andere ASGI-kompatible Protokolle verarbeitet oder ASGI-Middleware integriert werden muss.
Aufbau eines leistungsstarken WebSocket-Servers
Unser Ziel ist es, einen Server zu erstellen, der zahlreiche gleichzeitige WebSocket-Verbindungen effizient verarbeiten kann. Dies erfordert asynchrone Programmierung und sorgfältiges Ressourcenmanagement.
Einfacher Echo-Server
Beginnen wir mit einem einfachen Echo-Server. Wenn ein Client eine Nachricht sendet, sendet der Server sie einfach zurück. Dies demonstriert die Kernfunktionalität zum Senden und Empfangen.
# echo_server.py import asyncio import websockets async def echo(websocket, path): """ Asynchroner Handler für WebSocket-Verbindungen. Gibt jede vom Client empfangene Nachricht zurück. """ print(f"Client verbunden: {websocket.remote_address}") try: async for message in websocket: print(f"Nachricht von {websocket.remote_address} empfangen: {message}") await websocket.send(f"Echo: {message}") except websockets.exceptions.ConnectionClosedOK: print(f"Client {websocket.remote_address} sauber getrennt") except websockets.exceptions.ConnectionClosedError as e: print(f"Client {websocket.remote_address} mit Fehler getrennt: {e}") finally: print(f"Client getrennt: {websocket.remote_address}") async def main(): """ Startet den WebSocket-Server. """ # Startet den WebSocket-Server auf localhost, Port 8765 async with websockets.serve(echo, "localhost", 8765): await asyncio.Future() # Läuft unendlich if __name__ == "__main__": print("Starte WebSocket-Echo-Server auf ws://localhost:8765") asyncio.run(main())
Um dies auszuführen, speichern Sie es einfach als echo_server.py
und führen Sie python echo_server.py
aus. Sie können es dann mit einem einfachen JavaScript-Client in der Konsole Ihres Browsers testen:
const ws = new WebSocket("ws://localhost:8765"); ws.onopen = () => console.log("Verbunden"); ws.onmessage = (event) => console.log("Empfangen:", event.data); ws.send("Hallo, WebSocket!");
Integration mit ASGI für erweiterte Flexibilität
Obwohl die Funktion websockets.serve
hervorragend für eigenständige WebSocket-Anwendungen geeignet ist, bietet die Integration mit einem ASGI-Server wie Uvicorn Vorteile wie:
- Ausführung anderer ASGI-Anwendungen (z. B. REST-APIs) neben WebSockets auf demselben Server.
- Nutzung von ASGI-Middleware.
- Bessere Prozessverwaltung und Skalierungsfunktionen, die von produktionsreifen ASGI-Servern bereitgestellt werden.
So wickeln Sie unseren websockets
-Handler in eine ASGI-Anwendung ein. Die websockets
-Bibliothek bietet websockets.ASGIHandler
, um dies zu vereinfachen.
# asgi_websocket_server.py import asyncio import websockets from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError from websockets.sync.server import serve import uvicorn async def websocket_application(scope, receive, send): """ ASGI-kompatible WebSocket-Anwendung für unseren Echo-Server. """ if scope['type'] == 'websocket': async def handler(websocket): print(f"ASGI-Client verbunden: {websocket.remote_address}") try: async for message in websocket: print(f"ASGI-Nachricht von {websocket.remote_address} empfangen: {message}") await websocket.send(f"ASGI Echo: {message}") except ConnectionClosedOK: print(f"ASGI-Client {websocket.remote_address} sauber getrennt") except ConnectionClosedError as e: print(f"ASGI-Client {websocket.remote_address} mit Fehler getrennt: {e}") finally: print(f"ASGI-Client getrennt: {websocket.remote_address}") # Verwenden Sie das websockets.server.serve-Protokoll für ASGI # Dies erstellt eine AsyncWebSocketServerProtocol-Instanz für die Verbindung await websockets.server.serve_websocket(handler, scope, receive, send) else: # Behandeln Sie andere Anforderungstypen, falls erforderlich (z. B. HTTP für Health Checks) # Für einen reinen WebSocket-Server könnte dies nur ein Fehler sein. response_start = {'type': 'http.response.start', 'status': 404, 'headers': []} response_body = {'type': 'http.response.body', 'body': b'Not Found'} await send(response_start) await send(response_body) # Zum Ausführen mit Uvicorn: # uvicorn asgi_websocket_server:websocket_application --port 8000 --ws websockets
Um diesen ASGI-kompatiblen Server auszuführen:
- Installieren Sie Uvicorn:
pip install uvicorn websockets
- Führen Sie den Befehl aus:
uvicorn asgi_websocket_server:websocket_application --port 8000 --ws websockets
Das Flag --ws websockets
weist Uvicorn an, websockets
zur Verarbeitung von WebSocket-Verbindungen zu verwenden, um die Kompatibilität mit unserer Anwendung sicherzustellen. Ihr JavaScript-Client sollte nun auf ws://localhost:8000
zeigen.
Reales Beispiel: Ein einfacher Chatraum
Erweitern wir den Echo-Server zu einem einfachen Chatraum, um die Verarbeitung mehrerer Clients und das Senden von Nachrichten zu demonstrieren.
# chat_server.py import asyncio import websockets from websockets.exceptions import ConnectionClosedOK, ConnectionClosedError import json CONNECTED_CLIENTS = set() # Aktive WebSocket-Verbindungen speichern async def register(websocket): """Registriert eine neue Client-Verbindung.""" CONNECTED_CLIENTS.add(websocket) print(f"Client verbunden: {websocket.remote_address}. Gesamtclients: {len(CONNECTED_CLIENTS)}") async def unregister(websocket): """Hebt die Registrierung einer Client-Verbindung auf.""" CONNECTED_CLIENTS.remove(websocket) print(f"Client getrennt: {websocket.remote_address}. Gesamtclients: {len(CONNECTED_CLIENTS)}") async def broadcast_message(message): """Sendet eine Nachricht an alle verbundenen Clients.""" if CONNECTED_CLIENTS: # Sicherstellen, dass es Clients zum Senden gibt await asyncio.wait([client.send(message) for client in CONNECTED_CLIENTS]) async def chat_handler(websocket, path): """ Verarbeitet einzelne Client-Verbindungen im Chatraum. """ await register(websocket) try: user_name = None async for message_str in websocket: try: message_data = json.loads(message_str) message_type = message_data.get("type") if message_type == "join": user_name = message_data.get("name", "Anonymous") join_msg = json.dumps({"type": "status", "message": f"{user_name} ist dem Chat beigetreten."}) await broadcast_message(join_msg) elif message_type == "chat" and user_name: chat_msg = message_data.get("message", "") full_msg = json.dumps({"type": "chat", "sender": user_name, "message": chat_msg}) await broadcast_message(full_msg) else: await websocket.send(json.dumps({"type": "error", "message": "Ungültiges Nachrichtenformat oder nicht beigetreten." })) except json.JSONDecodeError: print(f"Ungültiges JSON von {websocket.remote_address} empfangen: {message_str}") await websocket.send(json.dumps({"type": "error", "message": "Ungültiges JSON-Format." })) except Exception as e: print(f"Fehler bei der Nachrichtenverarbeitung von {websocket.remote_address}: {e}") await websocket.send(json.dumps({"type": "error", "message": f"Serverfehler: {e}"})) except ConnectionClosedOK: print(f"Client {websocket.remote_address} sauber getrennt") except ConnectionClosedError as e: print(f"Client {websocket.remote_address} mit Fehler getrennt: {e}") finally: if user_name: leave_msg = json.dumps({"type": "status", "message": f"{user_name} hat den Chat verlassen."}) await broadcast_message(leave_msg) await unregister(websocket) async def main_chat_server(): """Startet den Chat-WebSocket-Server.""" async with websockets.serve(chat_handler, "localhost", 8766): await asyncio.Future() # Läuft unendlich if __name__ == "__main__": print("Starte WebSocket-Chat-Server auf ws://localhost:8766") asyncio.run(main_chat_server())
Dieser Chat-Server ermöglicht es Benutzern, sich mit einem Namen anzumelden und Nachrichten zu senden, die an alle gesendet werden. Er verwendet eine globale Menge CONNECTED_CLIENTS
, um aktive Verbindungen zu verfolgen, und asyncio.wait
für effizientes Broadcasting.
Anwendungsfälle
Ein eigenständiger, leistungsstarker WebSocket-Server, der mit websockets
und ASGI erstellt wurde, ist ideal für:
- Echtzeit-Dashboards: Anzeige von Live-Datenaktualisierungen (Börsenkurse, Sensorwerte, Analysen).
- Multiplayer-Spiele: Latenzarme Kommunikation für die Synchronisierung des Spielzustands.
- Live-Chat und Messaging: Erstellung benutzerdefinierter Chat-Anwendungen ohne Framework-Overhead.
- IoT-Gerätekommunikation: Empfangen von Echtzeit-Datenströmen von angeschlossenen Geräten.
- Benachrichtigungssysteme: Sofortiges Pushen von Benachrichtigungen an Clients.
Fazit: Echtzeit-Python-Anwendungen stärken
Durch die Kombination der Robustheit der websockets
-Bibliothek mit der Flexibilität und Standardisierung von ASGI können Python-Entwickler leistungsstarke, eigenständige WebSocket-Server erstellen, die anspruchsvolle Echtzeitkommunikationsanforderungen erfüllen können. Dieser Ansatz bietet detaillierte Kontrolle, hervorragende Leistung und einen klaren Weg zur Skalierung, was ihn zu einem unschätzbaren Muster für moderne, interaktive Webdienste macht. Die Nutzung dieser Tools ermöglicht es Python, als starker Mitbewerber im Bereich der Echtzeitanwendungen zu bestehen.