Introducción: Por qué WebSocket en C# es la elección de los profesionales
En el desarrollo web moderno, la interacción en tiempo real se ha convertido en un estándar. Plataformas de trading, sistemas de monitoreo, juegos en línea y chats: todos requieren transmisión instantánea de datos. La tecnología WebSocket en C# (.NET) proporciona una herramienta potente y flexible para crear este tipo de aplicaciones. A diferencia de las consultas HTTP, WebSocket establece una conexión bidireccional permanente, lo que reduce drásticamente la latencia y la carga del servidor.
En este artículo, exploraremos aspectos avanzados del trabajo con WebSocket en C#. Aprenderá cómo construir un servidor tolerante a fallos, gestionar miles de conexiones simultáneas, manejar errores e integrar WebSocket con frameworks populares. El material está dirigido a desarrolladores que ya conocen los fundamentos de .NET y la programación asíncrona.
Arquitectura de un servidor WebSocket avanzado en C#
Un ejemplo básico de servidor WebSocket en C# a menudo se limita a un solo manejador. Para soluciones de producción se requiere una arquitectura bien pensada. Veamos los componentes clave.
Gestión de conexiones e identificación de clientes
En lugar de una simple lista de conexiones, use ConcurrentDictionary para un almacenamiento seguro en subprocesos. A cada cliente se le asigna un identificador único (GUID) y se almacenan metadatos (por ejemplo, nombre de usuario, grupo).
public class WebSocketConnectionManager{ private readonly ConcurrentDictionary<string, WebSocket> _connections = new(); private readonly ConcurrentDictionary<string, UserMetadata> _users = new();
public string AddConnection(WebSocket socket, UserMetadata metadata) { var id = Guid.NewGuid().ToString(); _connections.TryAdd(id, socket); _users.TryAdd(id, metadata); return id; }
public async Task RemoveConnection(string id) { if (_connections.TryRemove(id, out var socket) && socket.State == WebSocketState.Open) { await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, "Closed", CancellationToken.None); } _users.TryRemove(id, out _); }
public WebSocket? GetSocketById(string id) => _connections.GetValueOrDefault(id);}Este enfoque permite enviar mensajes fácilmente a usuarios específicos, grupos o a todos a la vez.
Manejo de mensajes fragmentados
En aplicaciones reales, los datos pueden llegar en fragmentos. El método estándar ReceiveAsync devuelve un solo fragmento. Para ensamblar el mensaje completo, use un bucle:
public async Task<byte[]> ReceiveFullMessageAsync(WebSocket socket, CancellationToken ct){ using var ms = new MemoryStream(); WebSocketReceiveResult result; var buffer = new byte[1024 * 4]; // Buffer de 4KB
do { result = await socket.ReceiveAsync(new ArraySegment<byte>(buffer), ct); ms.Write(buffer, 0, result.Count); } while (!result.EndOfMessage);
return ms.ToArray();}Esto es crítico para la transmisión de objetos JSON grandes o datos binarios (imágenes, archivos).
Escalabilidad: SignalR vs Raw WebSocket
Para aplicaciones complejas surge la pregunta de elegir entre WebSocket de bajo nivel y SignalR, una biblioteca de Microsoft que abstrae WebSocket. Veamos los escenarios.
Cuándo usar Raw WebSocket
- Máximo rendimiento y mínimo overhead.
- Protocolo de transmisión propio (datos binarios, tramas personalizadas).
- Integración con clientes que no son .NET (por ejemplo, Go o Rust).
- Control total sobre la gestión de conexiones.
Cuándo elegir SignalR
- Escalabilidad automática mediante Redis Backplane o Azure SignalR Service.
- Soporte para transportes alternativos (Long Polling, Server-Sent