Flutter Android TCP应用如何检测ESP32控制器断电?
解决Flutter Socket检测ESP32意外断电的问题
首先明确核心原因:TCP连接在一端意外断电(未发送FIN断开包)时,客户端不会立刻收到系统层面的断开通知,因此Dart Socket的onDone/onError不会自动触发。你需要手动基于心跳实现超时检测,同时修正流监听的异常问题。
具体解决方案
1. 手动实现心跳超时判定
不要依赖Socket内置超时,自己维护连接活跃状态:
- 每次收到ESP32的任何消息(包括心跳响应),更新最后活跃时间戳
- 启动定时任务,定期检查最后活跃时间是否超出阈值(建议设为心跳间隔的1.5倍,比如你每分钟发心跳,阈值设90秒)
- 一旦超时,主动判定连接断开,触发离线处理逻辑
2. 修正Socket流监听异常
你使用asBroadcastStream()后若重复监听同一流,会触发“流已被监听”错误:
- 如果不需要多订阅,直接监听原始Socket流即可,无需转为广播流
- 若确实需要多订阅,确保只调用一次
asBroadcastStream()并复用这个广播流对象,不要重复创建
3. 主动写入验证连接状态
在发送心跳时,尝试向Socket写入数据,若连接已断,写入操作会抛出异常,此时可直接捕获并处理断开逻辑。
完整代码示例
import 'dart:async'; import 'dart:io'; import 'package:flutter/foundation.dart'; class LampSocketManager { Socket? _socket; StreamSubscription? _socketSubscription; Timer? _heartbeatTimer; Timer? _connectionCheckTimer; DateTime _lastActiveTime = DateTime.now(); static const int heartbeatInterval = 60; // 心跳间隔(秒) static const int connectionTimeout = 90; // 超时阈值(秒) // 初始化连接 Future<void> connect(String espHost, int espPort) async { try { _socket = await Socket.connect(espHost, espPort); _lastActiveTime = DateTime.now(); _setupSocketListener(); _startHeartbeat(); _startConnectionCheck(); } catch (e) { if (kDebugMode) print('连接ESP32失败: $e'); _handleDisconnection(); } } // 配置Socket监听 void _setupSocketListener() { // 直接监听原始Socket流,无需转为广播流(除非需要多订阅) _socketSubscription = _socket!.listen( (event) { String receivedMsg = String.fromCharCodes(event); _lastActiveTime = DateTime.now(); // 更新活跃时间 // 处理收到的消息:心跳响应、开关状态反馈等 if (kDebugMode) print('收到ESP32消息: $receivedMsg'); }, onDone: () { if (kDebugMode) print('连接正常关闭'); _handleDisconnection(); }, onError: (error) { if (kDebugMode) print('Socket错误: $error'); _handleDisconnection(); }, ); } // 启动心跳发送任务 void _startHeartbeat() { _heartbeatTimer?.cancel(); _heartbeatTimer = Timer.periodic(Duration(seconds: heartbeatInterval), (timer) { if (_socket != null && _socket!.isConnected) { try { _socket!.write('HEARTBEAT\n'); // 发送心跳指令 if (kDebugMode) print('发送心跳'); } catch (e) { if (kDebugMode) print('心跳发送失败: $e'); _handleDisconnection(); } } }); } // 启动连接状态检查 void _startConnectionCheck() { _connectionCheckTimer?.cancel(); _connectionCheckTimer = Timer.periodic(const Duration(seconds: 10), (timer) { final now = DateTime.now(); if (now.difference(_lastActiveTime).inSeconds > connectionTimeout) { if (kDebugMode) print('连接超时,判定ESP32断电'); _handleDisconnection(); } }); } // 统一处理断开逻辑 void _handleDisconnection() { _heartbeatTimer?.cancel(); _connectionCheckTimer?.cancel(); _socketSubscription?.cancel(); _socket?.destroy(); _socket = null; // 这里触发UI更新:比如显示设备离线、启动重连逻辑等 if (kDebugMode) print('已处理连接断开'); } // 主动断开连接 void disconnect() { _handleDisconnection(); } }
额外注意事项
- ESP32端需配合实现心跳响应:每次收到客户端的
HEARTBEAT指令,回复确认消息(比如HEARTBEAT_ACK),确保客户端能准确更新活跃时间 - 超时阈值要略大于心跳间隔,避免网络波动导致误判
- 若需要多订阅Socket流,仅调用一次
asBroadcastStream()并保存该流对象,后续订阅均复用此对象
内容的提问来源于stack exchange,提问作者Nefarious
相关产品推荐
相关产品推荐

