位置:首页 > Dart > Flutter实现TCP通信的关键步骤及代码示例

Flutter实现TCP通信的关键步骤及代码示例

时间:2026-08-17  |  作者:怪兽小助手  |  阅读:0

引言

在移动端开发里,除了大家熟悉的 HTTP、MQTT 之外,不少场景其实绕不开 TCP 通信

比如局域网设备控制、实时数据传输等场景,都经常会用到它。

这篇文章就来聊聊,在 Flutter/Dart 中实现一个 TCP 客户端到底该怎么写,以及关键代码该如何拆解。

同时,会给出自动重连和心跳保活的完整示例,方便直接拿去用。

Flutter中实现TCP通信的关键步骤与代码示例

1. 基本思路

先看整体实现流程。核心步骤主要有下面几项:

  • Socket.connect 建连,这是最直接的入口。
  • 连接建立后,把 socket 转成 流(Stream) 来监听,这样就能实时接收消息。
  • LineSplitter 按行切分消息。前提是每条数据以 n 结尾,且内容不含换行。这样就能避开 TCP 粘包/分包带来的麻烦。
  • 补上 超时/心跳自动重连(指数退避 + 抖动),客户端才更可靠。

2. 建立 TCP 连接(明文)

建连、监听、收发的基本骨架如下。

import 'dart:io';
import 'dart:async';
import 'dart:convert';

class TcpClient {
  final String host;
  final int port;
  Socket _socket;
  StreamSubscription _subscription;

  TcpClient(this.host, this.port);

  Future connect() async {
    try {
      _socket = await Socket.connect(host, port, timeout: const Duration(seconds: 5));
      print(' Connected to: ${_socket!.remoteAddress.address}:${_socket!.remotePort}');

      _subscription = _socket!
          .transform(utf8.decoder)
          .transform(const LineSplitter())
          .listen(_onLine, onError: _onError, onDone: _onDone);
    } catch (e) {
      print(' Connection failed: $e');
      rethrow;
    }
  }

  void _onLine(String line) {
    print(' Received line: $line');
  }

  void _onError(Object e, [StackTrace st]) {
    print(' Socket error: $e');
    disconnect();
  }

  void _onDone() {
    print(' Server closed connection');
    disconnect();
  }

  void send(String message) {
    final s = _socket;
    if (s != null) {
      s.write(message + 'n'); // 每条消息后加换行
      print(' Sent: $message');
    }
  }

  void disconnect() {
    _subscription.cancel();
    _subscription = null;
    _socket.destroy();
    _socket = null;
    print(' Disconnected');
  }
}

实现要点

  • 连接入口:通过 Socket.connect 发起 TCP 连接。
  • 消息监听:收到的数据先经过 utf8.decoder 解码,再交给 LineSplitter 按行拆分。
  • 发送格式:每次发送消息时,尾部补一个 n,方便服务端和客户端按行处理。
  • 异常处理:无论是 onError 还是 onDone,都及时断开并清理资源。

3. 心跳与空闲超时

连接建好后,还要考虑保活问题。

心跳管理器的作用,是定时发送心跳包,避免连接因为长时间空闲被服务端踢掉。

class HeartbeatManager {
  final void Function() onSendHeartbeat;
  final Duration interval;
  Timer? _timer;

  HeartbeatManager({required this.onSendHeartbeat, this.interval = const Duration(seconds: 30)});

  void start() {
    _timer ??= Timer.periodic(interval, (_) => onSendHeartbeat());
  }

  void stop() {
    _timer.cancel();
    _timer = null;
  }
}

这部分的作用

  • 按固定时间间隔发送心跳。
  • 在连接正常时维持活跃状态。
  • 断开连接时及时停止定时器,避免资源浪费。

4. 自动重连(指数退避 + 抖动)

掉线后不能一直傻等,也不能无脑高频重连。

更稳妥的方式,是使用指数退避加随机抖动。

这样既能避免频繁重连打爆服务器,也不会让重连等待时间过长。

import 'dart:math';

class ReconnectPolicy {
  final Duration minBackoff;
  final Duration maxBackoff;
  int _attempt = 0;
  final Random _rnd = Random();

  ReconnectPolicy({this.minBackoff = const Duration(seconds: 1), this.maxBackoff = const Duration(seconds: 30)});

  Duration nextDelay() {
    final base = minBackoff.inMilliseconds * pow(2, _attempt).toInt();
    final capped = min(base, maxBackoff.inMilliseconds);
    final jitter = (capped * (0.2 * (_rnd.nextDouble() * 2 - 1))).round();
    _attempt = min(_attempt + 1, 10);
    return Duration(milliseconds: max(0, capped + jitter));
  }

  void reset() => _attempt = 0;
}

策略特点

  • 指数退避:随着失败次数增加,重连等待时间逐步拉长。
  • 最大限制:等待时间不会无限增长,会被 maxBackoff 限制住。
  • 随机抖动:在固定退避时间上做轻微随机偏移,减少大量客户端同时重连的风险。
  • 成功重置:一旦连接成功,通过 reset() 把重试次数清零。

5. 最佳实践小结

  • 行分隔协议:发送端每条消息必须以 n 结尾,且消息体内不包含换行,否则切分会出问题。
  • 统一编码:收发都用 UTF8,省去编码转换的麻烦。
  • 心跳保活15~30 秒一次,如果收不到响应就触发重连。
  • 自动重连:搭配指数退避 + 抖动,控制重连频率。
  • 超时治理:连接超时、请求超时、空闲超时,一个都不能少。
  • 可观测性:建议埋点统计连接时延、失败原因、重连次数、心跳 RTT 等,线上排查问题会省很多力气。

6. 完整示例代码(可直接运行)

把上面几个模块拼在一起,就是一个能直接用的稳健 TCP 客户端。

import 'dart:async';
import 'dart:convert';
import 'dart:io';
import 'dart:math';

class ReconnectPolicy {
  final Duration minBackoff;
  final Duration maxBackoff;
  int _attempt = 0;
  final Random _rnd = Random();

  ReconnectPolicy({this.minBackoff = const Duration(seconds: 1), this.maxBackoff = const Duration(seconds: 30)});

  Duration nextDelay() {
    final base = minBackoff.inMilliseconds * pow(2, _attempt).toInt();
    final capped = min(base, maxBackoff.inMilliseconds);
    final jitter = (capped * (0.2 * (_rnd.nextDouble() * 2 - 1))).round();
    _attempt = min(_attempt + 1, 10);
    return Duration(milliseconds: max(0, capped + jitter));
  }

  void reset() => _attempt = 0;
}

class HeartbeatManager {
  final void Function() onSendHeartbeat;
  final Duration interval;
  Timer? _timer;

  HeartbeatManager({required this.onSendHeartbeat, this.interval = const Duration(seconds: 30)});

  void start() {
    _timer ??= Timer.periodic(interval, (_) => onSendHeartbeat());
  }

  void stop() {
    _timer.cancel();
    _timer = null;
  }
}

class RobustLineClient {
  final String host;
  final int port;
  Socket _socket;
  StreamSubscription _sub;

  final HeartbeatManager _hb;
  final ReconnectPolicy _policy = ReconnectPolicy();
  Timer _idleTimer;

  RobustLineClient({required this.host, required this.port})
      : _hb = HeartbeatManager(onSendHeartbeat: () {/* later bound */}, interval: const Duration(seconds: 20));

  Future start() async {
    await _connect();
  }

  Future _connect() async {
    try {
      _socket = await Socket.connect(host, port, timeout: const Duration(seconds: 5));
      print(' connected');
      _policy.reset();
      _hb.start();

      _sub = _socket!
          .transform(utf8.decoder)
          .transform(const LineSplitter())
          .listen(_onLine, onError: _onError, onDone: _onDone);

      // 心跳绑定到 send
      _hb.onSendHeartbeat.call = () => send('ping');

      _resetIdleTimeout();
    } catch (e) {
      print(' connect failed: $e');
      await _scheduleReconnect();
    }
  }

  void _onLine(String line) {
    _resetIdleTimeout();
    print(' $line');
  }

  void _onError(Object e, [StackTrace st]) {
    print(' $e');
    _teardown();
    _scheduleReconnect();
  }

  void _onDone() {
    print(' closed by server');
    _teardown();
    _scheduleReconnect();
  }

  void _teardown() {
    _idleTimer.cancel();
    _idleTimer = null;
    _hb.stop();
    _sub.cancel();
    _sub = null;
    _socket.destroy();
    _socket = null;
  }

  Future _scheduleReconnect() async {
    final delay = _policy.nextDelay();
    print(' reconnect in ${delay.inMilliseconds} ms');
    await Future.delayed(delay);
    await _connect();
  }

  void _resetIdleTimeout() {
    _idleTimer?.cancel();
    _idleTimer = Timer(const Duration(seconds: 60), () {
      print(' idle timeout -> reconnect');
      _teardown();
      _scheduleReconnect();
    });
  }

  void send(String message) {
    _socket.write(message + 'n');
  }
}

总结

在 Flutter/Dart 里实现 TCP 客户端,基础并不复杂。

关键在于把 连接、消息切分、心跳保活、空闲超时、自动重连 这几个部分组合完整。

如果只完成建连和收发,客户端只能算“能跑”。

把心跳和重连机制补齐,才算一个真正可用于实际场景的稳健 TCP 客户端。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多