基于websocket-sharp的Windows服务WebSocket长时间运行后无响应问题咨询
看起来你的Windows服务遇到了WebSocket连接静默失效的典型问题——运行一段时间后连接看似正常,但实际已经无法收发消息,连OnClose都没触发。结合websocket-sharp的特性和Slack的连接机制,我来分析下可能的原因和对应的解决办法:
可能的原因
1. TCP连接被中间设备静默断开
大多数防火墙、路由器或负载均衡器会主动断开长时间没有数据传输的TCP连接(通常超时时间在30分钟到24小时之间)。Slack的WebSocket连接如果长时间没有消息交互,就会触发这种情况。由于是静默断开(没有TCP FIN包),websocket-sharp无法感知连接已失效,自然不会触发OnClose事件,但实际上已经无法接收消息了。
2. 缺少心跳机制维持连接
WebSocket协议本身支持Ping/Pong心跳帧来确认连接存活状态。如果你的客户端没有定期发送Ping,Slack服务器可能会认为连接已死并断开,但更常见的是中间设备因为无数据先切断连接。websocket-sharp默认没有启用自动心跳,这会让连接更容易进入“假活”状态。
3. 异步事件中的未捕获异常
你的OnMessage事件处理用了async void,这种模式下如果ProcessDirectMessage或ProcessMessage抛出未处理异常,会直接终止事件处理线程,导致后续的OnMessage事件完全停止触发,但连接本身可能还处于Open状态,所以OnClose也不会触发。
具体解决办法
1. 启用websocket-sharp的自动心跳
websocket-sharp的WebSocket类提供了KeepAliveInterval属性,设置后会定期发送Ping帧给Slack服务器,服务器会回复Pong,既能维持连接,也能及时发现连接失效。
ws = new WebSocket(connection.Url); ws.KeepAliveInterval = TimeSpan.FromSeconds(30); // 每30秒发送一次Ping
建议设置30-60秒的间隔,既能避免中间设备断开,也不会给Slack服务器造成过多负担。
2. 实现自动重连机制
即使有心跳,也可能出现意外断开的情况(比如网络波动、Slack服务器重启),所以需要在连接断开后自动重试:
// 封装重连逻辑 private async Task ReconnectWebSocket() { const int retryDelayMinutes = 1; while (true) { try { if (ws.ReadyState != WebSocketState.Open) { // 先释放旧连接 if (ws.ReadyState != WebSocketState.Closed) { ws.Close(); } // 创建新连接并重新绑定事件 ws = new WebSocket(connection.Url); ws.KeepAliveInterval = TimeSpan.FromSeconds(30); InitializeWebSocketEvents(); // 把你的事件绑定逻辑抽成这个方法 ws.Connect(); System.Diagnostics.Debug.WriteLine("WebSocket重连成功"); break; } } catch (Exception ex) { System.Diagnostics.Debug.WriteLine($"重连失败,{retryDelayMinutes}分钟后重试: {ex.Message}"); await Task.Delay(TimeSpan.FromMinutes(retryDelayMinutes)); } } } // 在OnClose事件中触发重连 ws.OnClose += (sender, e) => { System.Diagnostics.Debug.WriteLine($"连接关闭: {e.Code} - {e.Reason}"); _ = ReconnectWebSocket(); // 异步执行,不阻塞当前线程 };
注意要把事件绑定的逻辑抽成单独的方法(比如InitializeWebSocketEvents),这样重连时可以重新绑定OnMessage、OnClose等事件。
3. 捕获异步事件中的所有异常
把async void事件处理中的代码用try-catch包裹,避免异常导致事件线程崩溃:
ws.OnMessage += async (sender, e) => { try { var msg = JsonConvert.DeserializeObject<MessageFromSlack>(e.Data); if (msg.Type == "message" && msg.Text != null && msg.User != UserId) { if (userMatcher.IsMatch(msg.Text)) { await ProcessDirectMessage(msg); } await ProcessMessage(msg); } if (msg.Type == "channel_joined") { await ChannelJoined(msg.ChannelModel.Id); } } catch (Exception ex) { // 建议用Windows事件日志或第三方日志库记录,Debug.WriteLine在服务中看不到 System.Diagnostics.EventLog.WriteEntry("SlackBotService", $"处理消息出错: {ex.Message}\n{ex.StackTrace}", System.Diagnostics.EventLogEntryType.Error); } };
4. 完善状态监控与日志
Windows服务运行时无法查看Debug输出,建议用Windows事件日志或NLog/Serilog等日志工具记录连接状态、消息处理、异常等关键信息,方便后续排查问题。比如在连接成功、重连、消息接收时都记录日志。
额外测试建议
- 本地模拟网络故障:禁用网卡再重新启用,测试重连逻辑是否能自动恢复连接。
- 检查Slack API限制:确保你的机器人没有触发Slack的请求限流(可以在Slack API控制台查看),限流可能导致连接异常。
内容的提问来源于stack exchange,提问作者Matt Burland

