Flutter中如何检测TCP Socket连接状态并监听断连事件
Dart Socket 连接状态判断与断开监听实现
Dart 标准库的Socket基于TCP协议实现,没有提供直接标记连接存活的布尔属性,连接状态需要结合事件监听、心跳机制共同判定,具体实现方案如下:
一、单次主动检查连接状态
仅能检查当前进程已经感知到的连接状态,无法识别网络静默断开(比如网线拔出、NAT超时、中间链路断网但双方都未收到断开报文的场景):
/// 返回true代表本地已确认连接处于可用状态 bool checkSocketAlive(Socket socket) { final rawSocket = socket.rawSocket; if (rawSocket == null) return false; // RawSocket状态为open时连接才可用,其余closed/readClosed/writeClosed都代表连接已不可用 return rawSocket.state == RawSocketState.open; }
如果需要100%准确判定连接可达,必须配合应用层心跳:给服务端发送约定的探测包,在超时时间内收到响应即可确认连接正常。
二、实时监听连接状态、断开自动触发通知
这是生产环境长连接场景的标准实现,不需要轮询,可覆盖所有断开场景:
- 利用Socket自带的事件流捕获正常断开、连接重置、网络错误等可直接感知的断开事件
- 加入定时心跳机制,补全静默断开场景的感知
- 所有断开场景统一触发回调,避免重复通知
完整实现代码:
import 'dart:async'; import 'dart:io'; class StableSocket { Socket? _socket; Timer? _heartbeatTimer; int _missedHeartbeatCount = 0; /// 连接断开时触发,参数为断开原因 final void Function(dynamic disconnectReason)? onDisconnect; /// 连接建立成功时触发 final void Function(Socket socket)? onConnect; /// 收到业务数据时触发 final void Function(List<int> data)? onData; // 心跳配置,可根据业务场景调整 final Duration heartbeatPeriod = const Duration(seconds: 45); final int maxMissedHeartbeat = 2; Future<void> connect(String host, int port) async { // 先清理之前的连接资源 _cleanup(); try { _socket = await Socket.connect(host, port); _missedHeartbeatCount = 0; onConnect?.call(_socket!); // 监听连接关闭事件 _socket!.done.then((_) => _triggerDisconnect("connection closed by remote")); // 监听socket数据流 _socket!.listen( (data) { // 收到任意数据都代表连接存活,重置心跳计数 _missedHeartbeatCount = 0; onData?.call(data); }, onError: (err) => _triggerDisconnect(err), onDone: () => _triggerDisconnect("socket stream closed"), cancelOnError: true, ); // 启动心跳探测 _startHeartbeat(); } catch (e) { _triggerDisconnect("connect failed: $e"); } } void _startHeartbeat() { _heartbeatTimer?.cancel(); _heartbeatTimer = Timer.periodic(heartbeatPeriod, (timer) { if (_socket == null) { timer.cancel(); return; } _missedHeartbeatCount++; if (_missedHeartbeatCount > maxMissedHeartbeat) { _triggerDisconnect("heartbeat timeout, no response from server"); return; } // 发送心跳包,字节内容需要和服务端约定一致,不要和业务报文冲突 _socket!.add([0x00]); }); } void _triggerDisconnect(dynamic reason) { if (_socket == null) return; _cleanup(); onDisconnect?.call(reason); } void _cleanup() { _heartbeatTimer?.cancel(); _socket?.close(); _socket = null; _missedHeartbeatCount = 0; } /// 主动断开连接 void close() { _triggerDisconnect("active close by local"); } }
注意事项
- 心跳包格式必须和服务端约定一致,避免被服务端当成非法报文断开连接
- 国内移动网络的NAT超时普遍在3-5分钟,心跳间隔设为30-60s即可兼顾流量消耗和连接可靠性
- 不要靠
_socket != null、socket.remoteAddress存在这类条件判断连接状态,Socket对象销毁前这些属性会一直保留有效值,哪怕连接已经断开
内容的提问来源于stack exchange,提问作者LeonardvV
相关产品推荐
相关产品推荐

