重启IIS为何能解决SignalR应用的Socket通信异常问题?
问题描述
我开发了一个基于SignalR的Web应用,通过Socket与一个无法控制的第三方服务器交互(无源码及协议文档)。每个SignalR客户端向Hub发送命令后,Hub会新建Socket连接,向第三方服务器发送命令并接收一到多个响应,再返回给客户端。
目前遇到的问题:第三方服务器会周期性停止向Hub返回响应,部署环境和本地调试都会出现。我为每个客户端命令创建独立Socket连接,接收完响应就关闭,但重启IIS总能解决问题。我搞不懂重启生效的原因,想找到根源彻底解决。
核心代码(已翻译为中文注释/文本)
public async Task SendCommand(string device, string? command) { var ipendPoint = new IPEndPoint(ipaddress, port); var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { ReceiveTimeout = (int)TimeSpan.FromSeconds(5).TotalMilliseconds, SendTimeout = (int)TimeSpan.FromSeconds(30).TotalMilliseconds, }; await socket.ConnectAsync(ipendPoint); try { while (true) { string? response = await SendCommandToServerAsync(socket, ipendPoint, device, command); command = null; if (response == null) { await Task.Delay(2000); continue; } if (response.Trim() != "GET&") { // 如果只收到'GET&'则不更新客户端,因为没有有效提示 var parsedResponse = ParseResponse(response); await Clients.Group(device).SendAsync("ReceiveResponse", device, parsedResponse); } if (response.Trim().EndsWith("GET&")) { break; } await Task.Delay(2000); } } finally { if (socket.Connected) { socket.Close(); } } } private async Task<string?> SendCommandToServerAsync(Socket socket, IPEndPoint ipendPoint, string device, string? command) { var responseSB = new StringBuilder(); var debugSB = new StringBuilder(); var text = device + "&" + command?.ToUpper() + "\n"; var bRequest = Encoding.UTF8.GetBytes(text.ToString()); debugSB.Append("请求内容: " + text); string? result; try { if (command != null) { debugSB.AppendLine("已发送请求"); await socket.SendAsync(bRequest); } else { debugSB.AppendLine("命令为空"); } await Task.Delay(900); byte[] array = new byte[8192]; int num2 = 0; string text2; while (true) { await Task.Delay(300); if (!socket.Connected) { debugSB.AppendLine("Socket未连接"); socket.Connect(ipendPoint); } int num3 = await socket.ReceiveAsync(array, SocketFlags.None); debugSB.AppendLine("收到 " + num3.ToString() + " 字节数据"); num2++; if (num3 > 0) { text2 = Encoding.Default.GetString(array, 0, num3); responseSB.AppendLine("收到内容: " + text2); string text3 = text2; text3 = text3.Replace("BEEP&" + '�', ""); if (!(text3 == "") && text2.IndexOf('�') >= 0) { break; } else { _emailSender.SendEmail("george.duckett@aqualisa.co.uk", "AquaBar 信息", "到达此处,响应内容: " + text2); } } else if (num2 == 20) { debugSB.AppendLine("跳转到Block_12"); goto Block_12; } } debugSB.AppendLine("退出循环"); string[] array2 = text2.Split(['�']); if (array2.Length > 2 && array2[0] == array2[1] && array2[0] != "BEEP&" && array2[0] != "PROMPT&") { text2 = array2[1]; } return text2.Replace('�', ' '); Block_12: result = "请按回车继续..GET&"; _emailSender.SendEmail("george.duckett@aqualisa.co.uk", "AquaBar 信息", "到达'请按回车继续'分支,响应内容: " + responseSB.ToString() + Environment.NewLine + Environment.NewLine + "调试信息: " + Environment.NewLine + debugSB.ToString()); } catch (SocketException ex) when (ex.SocketErrorCode == SocketError.TimedOut) { result = null; } catch (IOException) { result = null; } catch (Exception) { result = "BEEP& ***错误***\n服务器连接错误。\n请联系管理员。"; } return result; }
重启生效的可能原因
- Socket资源泄漏:虽然代码里在finally块关闭Socket,但异常场景下可能存在资源未完全释放的情况(比如
Socket.Close()没有彻底释放句柄)。重启IIS会清空进程所有资源,包括泄漏的Socket句柄,让新连接能正常建立。 - TCP连接TIME_WAIT堆积:每次关闭Socket后,TCP连接会进入TIME_WAIT状态(默认几分钟),如果请求量较大,本地会堆积大量TIME_WAIT连接,导致服务器端认为来自该IP的连接数超限,拒绝响应。重启IIS会清空本地TIME_WAIT连接池,恢复正常连接能力。
- 线程池耗尽:代码中Socket断开时调用同步的
socket.Connect(ipendPoint),在异步方法中阻塞线程,长期积累会耗尽ASP.NET线程池,后续请求无法处理。重启IIS会重置线程池,恢复处理能力。 - 协议解析阻塞:代码依赖特定终止符
'�'判断响应结束,如果服务器某次返回的响应没有这个符号,接收循环会一直等待直到超时,这类阻塞请求积累后会占用大量资源,重启IIS会清空这些阻塞的请求线程。
根源解决建议
- 用using语句管理Socket生命周期:确保无论是否异常都能彻底释放Socket资源,替换手动Close:
using var socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp) { ReceiveTimeout = (int)TimeSpan.FromSeconds(5).TotalMilliseconds, SendTimeout = (int)TimeSpan.FromSeconds(30).TotalMilliseconds, }; await socket.ConnectAsync(ipendPoint); - 优化TCP连接回收:设置LingerOption避免TIME_WAIT堆积:
socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Linger, new LingerOption(true, 0)); - 替换同步Connect为异步:Socket断开时用
await socket.ConnectAsync(ipendPoint)替代同步方法,避免阻塞线程。 - 增强解析逻辑容错性:在接收循环中增加时间超时判断,不仅计数num2,还要记录开始时间,超过设定时长(比如10秒)就终止循环并标记异常,避免无限等待。
- 完善日志监控:记录每个Socket连接的完整生命周期(建立、发送、接收、关闭)及原始响应数据,出现问题时快速定位是连接问题还是解析问题。
内容的提问来源于stack exchange,提问作者George Duckett
相关产品推荐
相关产品推荐

