WebSocket 在 C# 中的实际应用:从原型到生产环境——实时应用的高级技术
\n\n使用 System.Net.WebSockets 或 SignalR 等库在 C# 中实现标准 WebSocket 可以解决聊天和通知等基本任务。然而,当需求超出“Hello, World”的范围时,开发者就会面临扩展、安全性、自定义路由和性能优化的需求。在本文中,我们将深入探讨在 .NET 中使用 WebSocket 的高级技术,帮助您构建可靠且高负载的实时系统。
1. 可扩展 WebSocket 服务器的架构:模式与集群
\n\n标准 WebSocket 的主要限制之一是连接绑定到特定的进程实例。在水平扩展时,发送到服务器 A 的消息无法到达连接到服务器 B 的客户端。为了解决这个问题,有两种方法:
\n\n- \n
- 集中式后端平面(Backplane): 使用 Redis Pub/Sub、RabbitMQ 或 Azure SignalR Service 在服务器之间交换消息。 \n
- 粘性会话(Sticky Sessions): 负载均衡器始终将客户端请求定向到同一台服务器。这种方法可靠性较低,因为当节点宕机时,其所有客户端都会断开连接。 \n
让我们看一个使用 StackExchange.Redis 和 System.Net.WebSockets 在 Redis 上实现自定义后端平面的示例。
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 // 订阅频道 "websocket:messages"\n _subscriber.Subscribe("websocket:messages", (channel, message) => {\n var data = Encoding.UTF8.GetString(message);\n // 解析消息(假设格式为:"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 // 发布到 Redis,以便所有服务器都能收到消息\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. 自定义协议与路由:将 WebSocket 用作 RPC 传输
\n\n通常,我们不仅需要发送文本,还需要通过 WebSocket 调用远程过程(RPC)。与其手动解析 JSON 并检查命令,不如构建一个极简的消息路由器。SignalR 在底层使用了这种方法,但您可以为了实现完全控制而自行实现。
\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 // 期望格式为