diff --git a/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs b/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs index a4e4a6c..5fe1008 100644 --- a/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs +++ b/Windows/CS/Framework4.0/Toprie/Toprie/DeviceManager.cs @@ -266,6 +266,42 @@ namespace JoyD.Windows.CS.Toprie private Thread _temperatureReceiveThread; private bool _isReceivingTemperatureData = false; private bool _isTemperatureReceivingPaused = false; // 暂停标志,用于控制是否处理接收到的温度数据 + + /// + /// 准确检测TcpClient连接状态 + /// 不仅检查Connected属性,还通过Poll方法检测连接是否真正有效 + /// + /// 要检查的TcpClient实例 + /// 如果连接有效返回true,否则返回false + private bool IsTcpClientConnected(TcpClient client) + { + if (client == null || !client.Connected) + return false; + + // 检查连接状态的标准方法 + // 100毫秒超时,不读取数据只检测状态 + try + { + // Poll方法:检查连接状态,100ms超时 + // SelectMode.SelectRead:检查是否可读 + bool isConnected = !client.Client.Poll(100, System.Net.Sockets.SelectMode.SelectRead) || + client.Client.Available > 0; + + // 如果连接已断开,Client.RemoteEndPoint会抛出异常 + if (isConnected) + { + // 尝试访问RemoteEndPoint验证连接 + var dummy = client.Client.RemoteEndPoint; + } + + return isConnected; + } + catch + { + // 任何异常都表示连接可能已断开 + return false; + } + } private ManualResetEvent _stopTemperatureEvent; private const int TEMPERATURE_TCP_PORT = 8081; // 温度数据TCP端口 - 修正为热像仪SDK文档中指定的端口 @@ -1125,12 +1161,11 @@ namespace JoyD.Windows.CS.Toprie // 使用局部变量存储资源 TcpClient localTcpClient = null; NetworkStream localStream = null; - List temperatureDataAccumulator = new List(); + List temperatureDataAccumulator = new List(); byte[] buffer = new byte[65536]; // 缓冲区大小 // 定义常量 const int RECEIVE_TIMEOUT = 30000; // 增加到30秒,减少因超时导致的断开 - const int HEARTBEAT_INTERVAL = 15000; // 心跳间隔15秒 const int MEDIUM_SLEEP_MS = 50; const int LONG_SLEEP_MS = 2000; // 增加重连等待时间 const int ERROR_SLEEP_MS = 1000; // 增加异常恢复等待时间 @@ -1165,8 +1200,8 @@ namespace JoyD.Windows.CS.Toprie temperaturePort = TEMPERATURE_TCP_PORT; } - // 连接管理:仅当连接不存在或已断开时才创建新连接 - if (localTcpClient == null || !localTcpClient.Connected || localStream == null) + // 连接管理:当连接不存在、已断开或检测到连接不可用时创建新连接 + if (localTcpClient == null || !IsTcpClientConnected(localTcpClient) || localStream == null) { // 清理之前可能存在的连接资源 try @@ -1200,6 +1235,8 @@ namespace JoyD.Windows.CS.Toprie ReceiveTimeout = RECEIVE_TIMEOUT, SendTimeout = RECEIVE_TIMEOUT }; + // 设置接收缓冲区大小 + localTcpClient.ReceiveBufferSize = 65536; // 64KB Log($"正在连接到温度数据端口 {temperaturePort}..."); localTcpClient.Connect(deviceIp, temperaturePort); @@ -1259,7 +1296,7 @@ namespace JoyD.Windows.CS.Toprie // 记录上次心跳时间和上次接收数据时间 DateTime lastHeartbeatTime = DateTime.Now; DateTime lastReceiveTime = DateTime.Now; - + // 持续读取温度数据流 while (localTcpClient != null && localTcpClient.Connected) { @@ -1268,32 +1305,122 @@ namespace JoyD.Windows.CS.Toprie { isPaused = _isTemperatureReceivingPaused; } - + if (isPaused) { Log("温度接收已暂停,等待恢复"); Thread.Sleep(MEDIUM_SLEEP_MS); + // 在暂停状态下,仍然需要定期检查连接是否有效 + if (localTcpClient != null && !IsTcpClientConnected(localTcpClient)) + { + Log("暂停状态下检测到连接已断开,将在恢复时重建连接"); + try + { + if (localStream != null) + { + localStream.Close(); + localStream = null; + } + if (localTcpClient != null) + { + localTcpClient.Close(); + localTcpClient = null; + } + } + catch (Exception ex) + { + Log($"关闭无效连接时发生异常: {ex.Message}"); + } + } continue; } - - // 发送心跳保持连接 - if ((DateTime.Now - lastHeartbeatTime).TotalMilliseconds > HEARTBEAT_INTERVAL) + else { + // 从暂停状态恢复时,确保连接仍然有效,必要时重新启动数据传输 + if (localTcpClient != null && !IsTcpClientConnected(localTcpClient)) + { + Log("恢复接收时检测到连接无效,需要重建连接"); + try + { + if (localStream != null) + { + localStream.Close(); + localStream = null; + } + if (localTcpClient != null) + { + localTcpClient.Close(); + localTcpClient = null; + } + } + catch (Exception ex) + { + Log($"关闭无效连接时发生异常: {ex.Message}"); + } + // 跳出内层循环,回到外层循环重新建立连接 + break; + } + // 如果连接有效但数据传输可能已停止,尝试重新发送开始命令 + // 这是为了解决暂停后恢复时DataAvailable始终为false的问题 try { - byte[] heartbeatCommand = Encoding.ASCII.GetBytes("heartbeat\r\n"); - localStream.Write(heartbeatCommand, 0, heartbeatCommand.Length); + // 发送开始温度数据传输的命令 + byte[] startCommand = Encoding.ASCII.GetBytes("start_temp_transfer\r\n"); + localStream.Write(startCommand, 0, startCommand.Length); localStream.Flush(); - lastHeartbeatTime = DateTime.Now; - // 心跳不记录日志以减少日志量 + Log("从暂停状态恢复,重新发送开始温度数据传输命令"); } catch (Exception ex) { - Log($"发送心跳失败: {ex.Message}"); + Log($"重新发送开始命令失败: {ex.Message}"); + // 发送失败,可能连接已断开,需要重建连接 + try + { + if (localStream != null) + { + localStream.Close(); + localStream = null; + } + if (localTcpClient != null) + { + localTcpClient.Close(); + localTcpClient = null; + } + } + catch (Exception closeEx) + { + Log($"关闭失败连接时发生异常: {closeEx.Message}"); + } + // 跳出内层循环,回到外层循环重新建立连接 + break; } } - + // 使用非阻塞方式检查是否有数据可读 + // 先检查连接是否仍然有效 + if (localTcpClient != null && !IsTcpClientConnected(localTcpClient)) + { + Log("检测到连接已断开,准备重建连接"); + try + { + if (localStream != null) + { + localStream.Close(); + localStream = null; + } + if (localTcpClient != null) + { + localTcpClient.Close(); + localTcpClient = null; + } + } + catch (Exception ex) + { + Log($"关闭断开的连接时发生异常: {ex.Message}"); + } + continue; + } + if (localStream.DataAvailable) { // 有数据可读时进行阻塞读取 @@ -1301,9 +1428,7 @@ namespace JoyD.Windows.CS.Toprie if (bytesRead > 0) { lastReceiveTime = DateTime.Now; // 更新最后接收数据时间 - byte[] receivedBytes = new byte[bytesRead]; - Array.Copy(buffer, receivedBytes, bytesRead); - Log($"接收到温度数据字节数: {bytesRead}"); + Log($"==========================================接收到温度数据字节数: {bytesRead}"); // 根据暂停状态决定是否处理数据 if (isPaused) @@ -1312,39 +1437,46 @@ namespace JoyD.Windows.CS.Toprie } else { + byte[] receivedBytes = new byte[bytesRead]; + Array.Copy(buffer, receivedBytes, bytesRead); // 同步接收并处理数据 - lock (temperatureDataAccumulator) - { - temperatureDataAccumulator.AddRange(receivedBytes); - ProcessReceivedTemperatureData(temperatureDataAccumulator); - } + temperatureDataAccumulator.Add(receivedBytes); + ProcessReceivedTemperatureData(temperatureDataAccumulator); } } else if (bytesRead == 0) + { + // 连接已关闭 + Log("远程主机关闭了连接"); + + // 重置状态标志,下一次循环会创建新连接 + lock (_lockObject) { - // 连接已关闭 - Log("远程主机关闭了连接"); - - // 重置状态标志,下一次循环会创建新连接 - lock (_lockObject) - { - _isReceivingTemperatureData = false; - } - - // 清理连接资源 - try + _isReceivingTemperatureData = false; + } + + // 清理连接资源 + try + { + if (localStream != null) { localStream.Close(); - localTcpClient.Close(); + localStream = null; + } + if (localTcpClient != null) + { + localTcpClient.Close(); + localTcpClient = null; } - catch {} - - localStream = null; - localTcpClient = null; - continue; } + catch { } + + localStream = null; + localTcpClient = null; + continue; } - } + } + } // while循环退出后的处理 } catch (Exception ex) @@ -1422,43 +1554,43 @@ namespace JoyD.Windows.CS.Toprie /// 处理接收到的温度数据 /// /// 累积的温度数据 - private void ProcessReceivedTemperatureData(List dataAccumulator) + private void ProcessReceivedTemperatureData(List dataAccumulator) { - // 根据TemperatureData类的要求,每个温度帧包含9字节头部 + 温度数据 + //根据TemperatureData类的要求,每个温度帧包含9字节头部 + 温度数据 // 根据注释,设备实际提供的数据分辨率应为256x192,最终映射到512x384显示 const int HEADER_SIZE = 9; // 9字节头部("+TEMP"+数据长度) const int WIDTH = 256; const int HEIGHT = 192; const int TEMPERATURE_DATA_FRAME_SIZE = HEADER_SIZE + WIDTH * HEIGHT * 2; // 9字节头部 + 每个温度值2字节 - + try { Log($"开始处理温度数据,当前累积数据量: {dataAccumulator.Count} 字节,所需帧大小: {TEMPERATURE_DATA_FRAME_SIZE} 字节"); - + // 检查是否有足够的数据构成完整的温度数据帧 while (dataAccumulator.Count >= TEMPERATURE_DATA_FRAME_SIZE) { Log($"找到完整的温度数据帧,开始处理"); - + // 提取一帧温度数据 byte[] temperatureFrame = dataAccumulator.GetRange(0, TEMPERATURE_DATA_FRAME_SIZE).ToArray(); dataAccumulator.RemoveRange(0, TEMPERATURE_DATA_FRAME_SIZE); - + Log($"提取温度数据帧完成,剩余累积数据量: {dataAccumulator.Count} 字节"); - + // 获取温度补偿值 float compensationValue = GetTemperatureCompensationValue(); Log($"获取到温度补偿值: {compensationValue}"); - + // 创建温度数据对象,使用正确的分辨率参数 TemperatureData temperatureData = new TemperatureData(temperatureFrame, WIDTH, HEIGHT, compensationValue); Log($"温度数据对象创建成功,分辨率: {WIDTH}x{HEIGHT}"); - + // 触发温度数据接收事件 OnTemperatureReceived(new TemperatureReceivedEventArgs(temperatureData, temperatureFrame, compensationValue)); Log($"温度数据接收事件触发完成"); } - + Log($"温度数据处理完成"); } catch (Exception ex)