WebSocket en C# : du prototype à la production — techniques avancées pour applications temps réel
\n\nL'implémentation standard de WebSocket en C# à l'aide de bibliothèques comme System.Net.WebSockets ou SignalR résout les problèmes de base des chats et des notifications. Cependant, lorsque les exigences dépassent le cadre du "Hello, World", le développeur est confronté à la nécessité de mise à l'échelle, de sécurité, de routage personnalisé et d'optimisation des performances. Dans cet article, nous aborderons les techniques avancées de travail avec WebSocket dans .NET qui vous permettront de construire des systèmes temps réel fiables et à forte charge.
1. Architecture d'un serveur WebSocket évolutif : modèles et clustering
\n\nL'une des principales limitations du WebSocket standard est la liaison de la connexion à une instance de processus spécifique. Lors de la mise à l'échelle (horizontale), un message envoyé au serveur A n'atteindra pas le client connecté au serveur B. Pour résoudre ce problème, deux approches sont utilisées :
\n\n- \n
- Backplane centralisé : Utilisation de Redis Pub/Sub, RabbitMQ ou Azure SignalR Service pour échanger des messages entre les serveurs. \n
- Sessions persistantes (Sticky Sessions) : L'équilibreur de charge dirige toujours les requêtes du client vers le même serveur. Méthode moins fiable car en cas de panne d'un nœud, tous ses clients perdent la connexion. \n
Examinons un exemple d'implémentation d'un backplane personnalisé sur Redis en utilisant StackExchange.Redis et System.Net.WebSockets.
using StackExchange.Redis;\nusing System.Net.WebSockets;\nusing System.Text;\n\npublic class RedisBackplaneManager\n{\n private readonly ConnectionMultiplexer _redis;\n private readonly ISubscriber _subscriber;\n private readonly ConcurrentDictionary<string, WebSocket> _connections;\n\n public RedisBackplaneManager(string redisConnectionString)\n {\n _redis = ConnectionMultiplexer.Connect(redisConnectionString);\n _subscriber = _redis.GetSubscriber();\n _connections = new ConcurrentDictionary<string, WebSocket>();\n \n // Nous nous abonnons au canal "websocket:messages"\n _subscriber.Subscribe("websocket:messages", (channel, message) => {\n var data = Encoding.UTF8.GetString(message);\n // Nous analysons le message (format attendu : "connectionId:payload")\n var parts = data.Split(':', 2);\n if (parts.Length == 2 && _connections.TryGetValue(parts[0], out var ws))\n {\n ws.SendAsync(new ArraySegment<byte>(Encoding.UTF8.GetBytes(parts[1])), \n WebSocketMessageType.Text, true, CancellationToken.None);\n }\n });\n }\n\n public async Task SendToClientAsync(string connectionId, string message)\n {\n // Publication dans Redis pour que tous les serveurs reçoivent le message\n var payload = $"{connectionId}:{message}";\n await _subscriber.PublishAsync("websocket:messages", payload);\n }\n\n public void AddConnection(string id, WebSocket ws) => _connections.TryAdd(id, ws);\n public void RemoveConnection(string id) => _connections.TryRemove(id, out _);\n}\n\n2. Protocole personnalisé et routage : WebSocket comme transport RPC
\n\nIl est souvent nécessaire non seulement d'envoyer du texte, mais aussi d'appeler des procédures distantes (RPC) via WebSocket. Au lieu d'analyser manuellement le JSON et de vérifier les commandes, vous pouvez construire un routeur de messages minimaliste. Cette approche est utilisée sous le capot par SignalR, mais vous pouvez l'implémenter pour un contrôle total.
\n\npublic class WebSocketRouter\n{\n private readonly Dictionary<string, Func<string, Task>> _routes = new();\n\n public void On(string action, Func<string, Task> handler)\n {\n _routes[action] = handler;\n }\n\n public async Task RouteAsync(string rawMessage)\n {\n // Format attendu