WebSocket en C#: proyectos reales y técnicas avanzadas

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

WebSocket en C#: del prototipo a producción — técnicas avanzadas para aplicaciones en tiempo real

\n\n

La implementación estándar de WebSocket en C# utilizando bibliotecas como System.Net.WebSockets o SignalR resuelve tareas básicas de chats y notificaciones. Sin embargo, cuando los requisitos van más allá de "Hola, Mundo", el desarrollador se enfrenta a la necesidad de escalabilidad, seguridad, enrutamiento personalizado y optimización del rendimiento. En este artículo, analizaremos técnicas avanzadas para trabajar con WebSocket en .NET que le permitirán construir sistemas en tiempo real robustos y de alta carga.

\n\n

1. Arquitectura de un servidor WebSocket escalable: patrones y clustering

\n\n

Una de las principales limitaciones del WebSocket estándar es la vinculación de la conexión a una instancia específica del proceso. Al escalar (horizontalmente), un mensaje enviado al servidor A no llegará al cliente conectado al servidor B. Para resolver este problema se utilizan dos enfoques:

\n\n
    \n
  • Backplane centralizado: Uso de Redis Pub/Sub, RabbitMQ o Azure SignalR Service para intercambiar mensajes entre servidores.
  • \n
  • Sesiones persistentes (Sticky Sessions): El balanceador dirige las solicitudes del cliente siempre al mismo servidor. Método menos fiable, ya que si un nodo falla, todos sus clientes pierden la conexión.
  • \n
\n\n

Veamos un ejemplo de implementación de un backplane personalizado en Redis utilizando StackExchange.Redis y System.Net.WebSockets.

\n\n
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        // Nos suscribimos al canal "websocket:messages"\n        _subscriber.Subscribe("websocket:messages", (channel, message) => {\n            var data = Encoding.UTF8.GetString(message);\n            // Parseamos el mensaje (asumimos formato: "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        // Publicamos en Redis para que todos los servidores reciban el mensaje\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\n

2. Protocolo personalizado y enrutamiento: WebSocket como transporte RPC

\n\n

A menudo no solo es necesario enviar texto, sino invocar procedimientos remotos (RPC) a través de WebSocket. En lugar de analizar manualmente JSON y verificar comandos, se puede construir un enrutador de mensajes minimalista. Este enfoque se utiliza internamente en SignalR, pero usted puede implementarlo para tener control total.

\n\n
public 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        // Esperamos el forma                

Blogs

Book Recommendations