C# TCP/IP Socket连接打印系统运行1小时后崩溃问题排查
C# Socket通信客户端连接打印系统
问题概述
通过TCP/IP Socket与打印系统通信,采用每次循环新建连接→发送数据→关闭连接的流程,运行约1小时后程序直接崩溃,添加try-catch块后问题依旧。关闭连接时执行以下代码:
sender.Shutdown(SocketShutdown.Both); sender.Close();
崩溃日志
本地测试(随机报错DLL)
Faulting application name: PrintServerClient.exe, version: 1.0.0.0, time stamp: 0x8782029c Faulting module name: clr.dll, version: 4.8.4614.0, time stamp: 0x63995bc5 Exception code: 0xc0000005 Fault offset: 0x0000000000682e80 Faulting process id: 0x73c8 Faulting application start time: 0x01d98d81d24fc9a1 Faulting application path: C:\Users\username\source\repos\PrintServerClient\bin\Debug\net45\PrintServerClient.exe Faulting module path: C:\Windows\Microsoft.NET\Framework64\v4.0.30319\clr.dll Report Id: dfec97dc-0bbc-41ea-a9c4-b861460033bf Faulting package full name: Faulting package-relative application ID:
Windows 10 1909虚拟机测试(固定报错DNSAPI.dll)
Faulting application name: PrintServerClient.exe, version: 1.0.0.0, time stamp: 0x8bd9b096 Faulting module name: DNSAPI.dll, version: 10.0.18362.267, time stamp: 0x997f6419 Exception code: 0xc00000fd Fault offset: 0x0000000000009104 Faulting process id: 0x1f40 Faulting application start time: 0x01d98d85b37e0c32 Faulting application path: C:\BELLMARK\PrintServerClient.exe Faulting module path: C:\Windows\SYSTEM32\DNSAPI.dll Report Id: 9063cd93-208a-4bea-90b6-a635fd439ed2 Faulting package full name: Faulting package-relative application ID:
客户端代码片段
private void StartClient(byte[] PrintMsg) { byte[] bytes = new byte[32]; DateTime currentDateTime = DateTime.Now; if (ButtonStart.Visibility == Visibility.Visible) { try { // Connect to a Remote server // Get Host IP Address that is used to establish a connection // In this case, we get one IP address of localhost that is IP : 127.0.0.1 // If a host has multiple addresses, you will get a list of addresses IPHostEntry host = Dns.GetHostEntry("localhost"); IPAddress ipAddress = host.AddressList[1]; IPEndPoint remoteEP = new IPEndPoint(ipAddress, 4500); Socket sender = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // Connect the socket to the remote endpoint. Catch any errors. try { // Connect to Remote EndPoint sender.Connect(remoteEP); //Console.WriteLine("Socket connected to {0}", sender.RemoteEndPoint.ToString()); // Encode the data string into a byte array. //byte[] msg = Encoding.ASCII.GetBytes("This is a test<EOF>"); //byte[] msg = Encoding.ASCII.GetBytes("<STX>#<ETX>"); byte[] STOP_PRINT = { 02, 17, 03 }; // Send the data through the socket. int bytesSent = sender.Send(STOP_PRINT); // Receive the response from the remote device. int bytesRec = sender.Receive(bytes); //Console.WriteLine("Echoed test = {0}", Encoding.ASCII.GetString(bytes, 0, bytesRec)); // Release the socket. try { sender.Shutdown(SocketShutdown.Both); sender.Close(); } catch { } } catch (ArgumentNullException ane) { //Console.WriteLine("ArgumentNullException : {0}", ane.ToString()); } catch (SocketException se) { //Console.WriteLine("SocketException : {0}", se.ToString()); } catch (Exception e) { //Console.WriteLine("Unexpected exception : {0}", e.ToString()); } } catch (Exception e) { //Console.WriteLine(e.ToString()); } } else if (ButtonStart.Visibility == Visibility.Hidden) { try { // Connect to a Remote server // Get Host IP Address that is used to establish a connection // In this case, we get one IP address of localhost that is IP : 127.0.0.1 // If a host has multiple addresses, you will get a list of addresses IPHostEntry host = Dns.GetHostEntry("localhost"); IPAddress ipAddress = host.AddressList[1]; IPEndPoint remoteEP = new IPEndPoint(ipAddress, 4500); // Create a TCP/IP socket. Socket sender = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp); // Connect the socket to the remote endpoint. Catch any errors. try { // Connect to Remote EndPoint sender.Connect(remoteEP); //Console.WriteLine("Socket connected to {0}", sender.RemoteEndPoint.ToString()); // Send the data through the socket. int bytesSent = sender.Send(PrintMsg); // Receive the response from the remote device. int bytesRec = sender.Receive(bytes); //Console.WriteLine("Echoed test = {0}", // Encoding.ASCII.GetString(bytes, 0, bytesRec)); string hex = BitConverter.ToString(bytes); string hexString = hex.Replace("-", ""); string byte1 = hexString.Substring(0, 2); string byte2 = hexString.Substring(2, 2); string byte3 = hexString.Substring(4, 2); string byte4 = hexString.Substring(6, 2); try { sender.Shutdown(SocketShutdown.Both); sender.Close(); if (byte3 == "06") { InternalPrintCounter++; string ReturnMessage = byte1 + " " + byte2 + " " + byte3 + " " + byte4 + "--> ACK - " + currentDateTime; if (InternalPrintCounter > 5) { InternalPrintCounter = 0; DynamicDataQueueCount(ReturnMessage); } else { SerialNumberGenerator(ReturnMessage); } } else if (byte3 == "15") { } } catch { } } catch (ArgumentNullException ane) { //Console.WriteLine("ArgumentNullException : {0}", ane.ToString()); } catch (SocketException se) { //Console.WriteLine("SocketException : {0}", se.ToString()); } catch (Exception e) { //Console.WriteLine("Unexpected exception : {0}", e.ToString()); } } catch (Exception e) { //Console.WriteLine(e.ToString()); } } }
问题原因分析
- 资源泄漏:Socket未被正确释放。手动调用
Close()和Shutdown()可能因异常被跳过,且未实现IDisposable的正确释放逻辑,导致系统句柄或内存泄漏,长时间运行后触发崩溃。 - DNS查询开销与错误:每次循环调用
Dns.GetHostEntry("localhost"),频繁的DNS查询会消耗系统资源;硬编码host.AddressList[1]存在风险,若主机地址列表长度不足2会引发索引越界,空catch块吞掉了这类错误,导致问题隐藏。 - 空catch块吞噬异常:多处空
catch块会掩盖连接、关闭过程中的错误,无法及时排查潜在问题,异常积累后引发崩溃。 - Socket接收逻辑不完整:
sender.Receive(bytes)仅接收一次数据,TCP是流协议,可能存在单次接收不完整的情况,残留数据或错误状态未处理,影响后续连接。 - 频繁创建连接:每次循环新建Socket连接,会频繁占用和释放网络资源,增加系统负载,长时间运行后触发资源耗尽类崩溃。
修复建议
1. 使用using语句管理Socket资源
Socket实现了IDisposable接口,使用using可自动释放资源,替代手动Close()/Shutdown(),确保资源无论是否异常都能被正确回收:
using (Socket sender = new Socket(ipAddress.AddressFamily, SocketType.Stream, ProtocolType.Tcp)) { sender.Connect(remoteEP); // 发送、接收逻辑 sender.Shutdown(SocketShutdown.Both); // 可选,确保数据传输完成 } // 自动调用Dispose(),释放Socket资源
2. 优化IP地址获取逻辑
避免频繁DNS查询,直接使用IPAddress.Loopback(对应127.0.0.1)代替Dns.GetHostEntry("localhost"),减少开销并避免索引错误:
IPAddress ipAddress = IPAddress.Loopback; IPEndPoint remoteEP = new IPEndPoint(ipAddress, 4500);
若需解析远程主机,需判断AddressList长度后再选择,而非硬编码索引。
3. 移除空catch块,添加异常日志
空catch块会隐藏错误,至少记录异常信息,便于排查问题:
catch (Exception ex) { // 写入日志文件或输出到调试窗口 System.Diagnostics.Debug.WriteLine($"Socket操作异常: {ex.ToString()}"); }
4. 实现完整的Socket接收逻辑
TCP是流协议,需循环接收直到获取完整响应数据,示例如下:
int totalBytesReceived = 0; int expectedBytes = 4; // 根据协议预期的响应长度 while (totalBytesReceived < expectedBytes) { int bytesRec = sender.Receive(bytes, totalBytesReceived, expectedBytes - totalBytesReceived, SocketFlags.None); if (bytesRec == 0) { throw new SocketException((int)SocketError.ConnectionReset); } totalBytesReceived += bytesRec; } // 处理完整的响应数据
5. 复用Socket连接(可选)
若打印系统支持长连接,可复用同一Socket连接,避免频繁创建/销毁连接,减少资源消耗:
- 初始化时创建一次Socket连接,保持连接状态
- 循环发送数据时直接使用该连接,仅在连接断开时重新建立
6. 排查资源泄漏
使用Visual Studio诊断工具(诊断工具→内存/CPU)监控程序运行时的句柄、内存使用情况,确认是否存在资源泄漏;也可使用Windows的Resource Monitor查看进程的句柄计数变化。
内容的提问来源于stack exchange,提问作者macflirty
相关产品推荐
相关产品推荐

