WebSocket in Rust: fortgeschrittene Echtzeitanwendungen
\n\nRust ist aufgrund seiner Sicherheit und der Nullkosten-Abstraktionen die ideale Sprache für die Entwicklung leistungsstarker Echtzeitanwendungen. In diesem Artikel betrachten wir fortgeschrittene Techniken für die Arbeit mit WebSocket: von der Skalierung bis zur Autorisierung und Fehlerbehandlung. Sie erfahren, wie Sie einen production-ready WebSocket-Server erstellen, der tausende gleichzeitige Verbindungen bedienen kann.
\n\nArchitektur eines WebSocket-Servers mit Tokio und Tungstenite
\n\nFür den Aufbau eines zuverlässigen Servers verwenden wir die Kombination aus tokio (asynchrone Laufzeitumgebung) und tokio-tungstenite (WebSocket-Bibliothek). Die fortgeschrittene Architektur umfasst einen Verbindungspool, Graceful Shutdown und Fehlerbehandlung.
use tokio::net::TcpListener;\nuse tokio_tungstenite::accept_async;\nuse futures_util::{SinkExt, StreamExt};\nuse std::sync::Arc;\nuse tokio::sync::Mutex;\n\ntype PeerMap = Arc<Mutex<HashMap<String, PeerConnection>>>;\n\n#[derive(Clone)]\nstruct PeerConnection {\n sender: mpsc::UnboundedSender<Message>,\n user_id: String,\n}\n\nasync fn handle_connection(\n peer_map: PeerMap,\n raw_stream: TcpStream,\n addr: SocketAddr,\n) {\n let ws_stream = accept_async(raw_stream)\n .await\n .expect("Fehler beim Handshake");\n \n let (mut write, mut read) = ws_stream.split();\n let (tx, mut rx) = mpsc::unbounded_channel::<Message>();\n \n // Registrierung im Pool\n let user_id = Uuid::new_v4().to_string();\n peer_map.lock().await.insert(\n user_id.clone(),\n PeerConnection { sender: tx, user_id: user_id.clone() }\n );\n \n // Aufgaben starten\n let send_task = tokio::spawn(async move {\n while let Some(msg) = rx.recv().await {\n if write.send(msg).await.is_err() {\n break;\n }\n }\n });\n \n let recv_task = tokio::spawn(async move {\n while let Some(Ok(msg)) = read.next().await {\n broadcast_message(&peer_map, &user_id, &msg).await;\n }\n });\n \n // Graceful Shutdown\n tokio::select! {\n _ = send_task => {},\n _ = recv_task => {},\n }\n \n peer_map.lock().await.remove(&user_id);\n println!("Client {} getrennt", addr);\n}\n\n\nDieser Code zeigt die grundlegende Architektur: Verbindungspool, Duplex-Kommunikation und automatische Entfernung bei Trennung.
\n\nSkalierung: Tausende Verbindungen und Clusterbildung
\n\nFür die Verarbeitung tausender Verbindungen verwenden wir mehrere Schlüsselmuster:
\n\n- \n
- Horizontale Skalierung — Start mehrerer Serverinstanzen mit einem Load Balancer \n
- Redis Pub/Sub — Nachrichtenaustausch zwischen Instanzen \n
- Sharding — Verteilung nach user_id \n
Beispiel für die Integration mit Redis für den serverübergreifenden Austausch:
\n\nuse redis::AsyncCommands;\nuse serde::{Serialize, Deserialize};\n\n#[derive(Serialize, Deserialize)]\nstruct ClusterMessage {\n from: String,\n to: String,\n payload: String,\n timestamp: u64,\n}\n\nasync fn handle_cluster_message(\n redis_conn: &mut redis::aio::Connection,\n peer_map: PeerMap,\n channel: String,\n) {\n let mut pubsub = redis_conn.as_pubsub();\n pubsub.subscribe(channel).await.unwrap();\n \n let mut stream = pubsub.on_message();\n \n while let Some(msg) = stream.next().await {\n let payload: String = msg.get_payload().unwrap();\n let cluster_msg: ClusterMessage = serde_json::from_str(&payload).unwrap();\n \n // An lokalen Client senden\n if let Some(peer) = peer_map.lock().await.get(&cluster_msg.to) {\n let _ = peer.sender.send(Message::Text(cluster_msg.payload.into()));\n }\n }\n}\n\n\nFür die Lastverteilung verwenden wir Consistent Hashing. Dies gewährleistet eine gleichmäßige Verteilung der