Flutter客户端如何等待Socket.IO连接成功后获取socket.id
Flutter Socket.IO 登出后重连无法可靠获取socket.id 解决方案
问题背景
- Flutter客户端集成Socket.IO,后端为基础Node.js服务
- 初始在
main.dart中调用socket.connect()可正常建立连接,获取socket.id完成模拟登录 - 调用
signout()时执行socket.disconnect()断开连接流程正常 - 后续调用
signin()重连时,原硬编码延迟的方案在网络波动时容易失效,还会造成不必要的等待,没有可靠机制等待连接建立完成、获取socket.id赋值给登录用户
核心实现思路
利用Dart的Completer手动管理连接完成的异步状态,在Socket的connect/reconnect事件触发时完成completer返回socket.id,连接报错时抛出异常,signin方法中直接await这个Future即可100%等待连接就绪,不需要写死等待时长。
完整实现代码
1. 修改SocketRepository
import 'dart:async'; import 'package:socket_io_client/socket_io_client.dart' as io; class SocketRepository { late io.Socket socket; Auth auth = Auth.instance; // 用于管理连接完成状态的Completer Completer<String?> _socketIdCompleter = Completer(); // 对外暴露等待连接完成、获取socketId的Future Future<String?> get socketId => _socketIdCompleter.future; static final SocketRepository _socketRepo = SocketRepository._internal(); factory SocketRepository() { return _socketRepo; } SocketRepository._internal() { socket = io('http://127.0.0.1:3000', <String, dynamic>{ 'transports': ['websocket'], 'autoConnect': false, }); // 连接成功回调 socket.onConnect((_) { print('socket connected: ${socket.id}'); auth.socketId = socket.id; // 连接成功时完成completer,返回socketId if (!_socketIdCompleter.isCompleted) { _socketIdCompleter.complete(socket.id); } }); // 重连成功回调 socket.on('reconnect', (_) { auth.socketId = socket.id; if (!_socketIdCompleter.isCompleted) { _socketIdCompleter.complete(socket.id); } }); // 连接错误回调 socket.on("error", (data) { if (!_socketIdCompleter.isCompleted) { _socketIdCompleter.completeError(data); } }); // 断开连接回调 socket.onDisconnect((_) { // 可在这里做连接断开后的清理逻辑 }); } void connect() { // 每次发起新连接前重置completer if (_socketIdCompleter.isCompleted) { _socketIdCompleter = Completer(); } socket.connect(); } void disconnect() { socket.disconnect(); // 断开时如果还在等待连接结果,结束等待返回null if (!_socketIdCompleter.isCompleted) { _socketIdCompleter.complete(null); } } }
2. 修改登录登出逻辑
class UserRepository { Auth auth = Auth.instance; final socketRepo = SocketRepository(); Future<User> signIn() async { try { await Future.delayed(const Duration(seconds: 1)); // 先判断当前是否已连接 if (socketRepo.socket.id == null) { print('start reconnect socket'); socketRepo.connect(); // 等待连接完成,拿到socketId,不需要硬编码延迟 final socketId = await socketRepo.socketId; auth.socketId = socketId; } final loginUser = User( id: 1, socketId: socketRepo.socket.id, userName: 'Logged in User' ); auth.currentUser = loginUser; print('login success, user info: $loginUser'); return auth.currentUser; } catch (e) { print('login error: $e'); rethrow; } } Future<User> signOut() async { try { await Future.delayed(const Duration(seconds: 1)); auth.currentUser = User.empty; socketRepo.disconnect(); print('sign out success'); return auth.currentUser; } catch (e) { print('sign out error: $e'); rethrow; } } }
方案优势
- 不需要硬编码等待时长,连接成功后立即返回,弱网环境下也能稳定拿到socket.id
- 连接出错时会直接抛出异常,方便上层做错误提示、重试逻辑
- 单例SocketRepository全局管理连接状态,补全了completer状态判断,不会出现多次触发回调、内存泄漏问题
- 自动适配首次连接、登出后重连、异常断连重连等多种场景
内容的提问来源于stack exchange,提问作者Marcel Dz
相关产品推荐
相关产品推荐

