feat: native 重连机制优化

This commit is contained in:
chaoszhu
2026-07-04 20:15:07 +08:00
parent c08513b91b
commit d16710501d
2 changed files with 36 additions and 6 deletions
@@ -50,8 +50,15 @@ class SshTerminalController {
try { try {
final transport = await _transportFactory.open( final transport = await _transportFactory.open(
config, config,
logger: (message) => terminal.write('[Info] $message\r\n'), logger: (message) {
if (_disposed) return;
terminal.write('[Info] $message\r\n');
},
); );
if (_disposed) {
await transport.close();
return;
}
_transport = transport; _transport = transport;
terminal.write('[Info] 准备连接目标终端: ${config.name} - ${config.host}\r\n'); terminal.write('[Info] 准备连接目标终端: ${config.name} - ${config.host}\r\n');
// dartssh2 `SSHKeyPair.fromPem` already returns `List<SSHKeyPair>`, no // dartssh2 `SSHKeyPair.fromPem` already returns `List<SSHKeyPair>`, no
@@ -68,18 +75,24 @@ class SshTerminalController {
identities: identities, identities: identities,
); );
} on SshTransportException catch (error) { } on SshTransportException catch (error) {
if (_disposed) return;
terminal.write('[Error] ${error.message}\r\n'); terminal.write('[Error] ${error.message}\r\n');
rethrow; rethrow;
} catch (error) { } catch (error) {
if (_disposed) return;
terminal.write('[Error] $error\r\n'); terminal.write('[Error] $error\r\n');
rethrow; rethrow;
} }
if (_disposed) return;
final session = await _client!.shell(); final session = await _client!.shell();
if (_disposed) return;
_session = session; _session = session;
_stdoutSub = session.stdout.listen((data) { _stdoutSub = session.stdout.listen((data) {
if (_disposed) return;
_writeTerminalOutput(utf8.decode(data, allowMalformed: true)); _writeTerminalOutput(utf8.decode(data, allowMalformed: true));
}); });
_stderrSub = session.stderr.listen((data) { _stderrSub = session.stderr.listen((data) {
if (_disposed) return;
_writeTerminalOutput(utf8.decode(data, allowMalformed: true)); _writeTerminalOutput(utf8.decode(data, allowMalformed: true));
}); });
terminal.onOutput = (data) { terminal.onOutput = (data) {
@@ -140,6 +153,8 @@ class SshTerminalController {
Future<void> disconnect() async { Future<void> disconnect() async {
_disposed = true; _disposed = true;
terminal.onOutput = null;
terminal.onResize = null;
await _stdoutSub?.cancel(); await _stdoutSub?.cancel();
await _stderrSub?.cancel(); await _stderrSub?.cancel();
_stdoutSub = null; _stdoutSub = null;
@@ -74,12 +74,18 @@ class TerminalSessionManager extends ChangeNotifier {
final session = _findOrNull(id); final session = _findOrNull(id);
if (session == null) return; if (session == null) return;
final terminal = session.controller.terminal; final terminal = session.controller.terminal;
await session.controller.disconnect(); // Force-disconnect regardless of current state (connecting/connected/error).
final oldController = session.controller;
session.controller = SshTerminalController( session.controller = SshTerminalController(
config: session.config, config: session.config,
terminal: terminal, terminal: terminal,
); );
session.controller.terminal.write('\r\n[Reconnecting]\r\n'); session.status = TerminalSessionStatus.connecting;
notifyListeners();
// Tear down the old controller in the background — it no longer owns the
// terminal so its disconnect cannot interfere with the new connection.
unawaited(oldController.disconnect());
terminal.write('\r\n[Reconnecting]\r\n');
await _connect(session); await _connect(session);
} }
@@ -112,12 +118,18 @@ class TerminalSessionManager extends ChangeNotifier {
} }
Future<void> _connect(TerminalSession session) async { Future<void> _connect(TerminalSession session) async {
final controller = session.controller;
session.status = TerminalSessionStatus.connecting; session.status = TerminalSessionStatus.connecting;
session.lastError = null; session.lastError = null;
notifyListeners(); notifyListeners();
try { try {
await session.controller.connect(); await controller.connect();
if (!_sessions.contains(session)) return; // After await, verify this controller is still the active one — a
// concurrent reconnect may have replaced it.
if (!_sessions.contains(session) ||
session.controller != controller) {
return;
}
session.status = TerminalSessionStatus.connected; session.status = TerminalSessionStatus.connected;
notifyListeners(); notifyListeners();
if (_shouldAutoStartStatusMonitor()) { if (_shouldAutoStartStatusMonitor()) {
@@ -129,7 +141,10 @@ class TerminalSessionManager extends ChangeNotifier {
); );
} }
} catch (error) { } catch (error) {
if (!_sessions.contains(session)) return; if (!_sessions.contains(session) ||
session.controller != controller) {
return;
}
session.status = TerminalSessionStatus.error; session.status = TerminalSessionStatus.error;
session.lastError = error.toString(); session.lastError = error.toString();
session.controller.terminal.write('\r\n[Error] ${session.lastError}\r\n'); session.controller.terminal.write('\r\n[Error] ${session.lastError}\r\n');