Flutter中實(shí)現(xiàn)TCP通信的關(guān)鍵步驟與代碼示例
引言
在移動端開發(fā)中,除了常見的 HTTP、MQTT 之外,很多場景需要直接使用 TCP 通信,例如局域網(wǎng)設(shè)備控制、實(shí)時傳輸?shù)取1疚膶⒔榻B在 Flutter/Dart 中實(shí)現(xiàn)一個 TCP 客戶端的基本過程,并解析關(guān)鍵代碼點(diǎn)。文章同時給出 自動重連 與 心跳?;?/strong> 的完整示例代碼,便于直接落地。
1. 基本思路
- 使用
Socket.connect建立連接 - 將
socket轉(zhuǎn)換為 流(Stream) 進(jìn)行監(jiān)聽,實(shí)時接收消息 - 使用
LineSplitter按行切分消息,避免 TCP 粘包/分包問題(前提:每條消息以\n結(jié)尾,且內(nèi)容不含換行) - 加入 超時/心跳 與 自動重連(指數(shù)退避 + 抖動)
2. 建立 TCP 連接(明文)
import 'dart:io';
import 'dart:async';
import 'dart:convert';
class TcpClient {
final String host;
final int port;
Socket? _socket;
StreamSubscription<String>? _subscription;
TcpClient(this.host, this.port);
Future<void> 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');
}
}
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. 自動重連(指數(shù)退避 + 抖動)
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;
}
5. 最佳實(shí)踐小結(jié)
- 行分隔協(xié)議:確保發(fā)送端每條消息都以
\n結(jié)尾,且消息體不包含換行 - 統(tǒng)一編碼:收發(fā)都用 UTF?8
- 心跳?;?/strong>:15–30 秒 1 次,收不到響應(yīng) → 重連
- 自動重連:指數(shù)退避 + 抖動
- 超時治理:連接超時、請求超時、空閑超時
- 可觀測性:埋點(diǎn)連接時延、失敗原因、重連次數(shù)、心跳 RTT 等
6. 完整示例代碼(可直接運(yùn)行)
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<String>? _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<void> start() async {
await _connect();
}
Future<void> _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<void> _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中實(shí)現(xiàn)TCP通信的關(guān)鍵步驟與代碼示例的詳細(xì)內(nèi)容,更多關(guān)于Flutter實(shí)現(xiàn)TCP通信的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android畫圖并保存圖片的具體實(shí)現(xiàn)代碼
這篇文章介紹了在Android中畫圖并保存圖片的實(shí)例,以下是具體的實(shí)現(xiàn)方法,有需要的朋友可以參考一下2013-07-07
Android 通過Base64上傳圖片到服務(wù)器實(shí)現(xiàn)實(shí)例
這篇文章主要介紹了Android 通過Base64上傳圖片到服務(wù)器實(shí)現(xiàn)實(shí)例的相關(guān)資料,需要的朋友可以參考下2017-05-05
android加密參數(shù)定位實(shí)現(xiàn)方法
這篇文章主要介紹了android加密參數(shù)定位方法,本文給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2021-04-04
Android拍照保存在系統(tǒng)相冊不顯示的問題解決方法
我們保存相冊到Android手機(jī)的時候,然后去打開系統(tǒng)圖庫找不到我們想要的那張圖片,那是因?yàn)槲覀儾迦氲膱D片還沒有更新的緣故,下面與大家分享下此問題的解決方法2013-06-06
android studio無法添加 bmob sdk依賴問題及解決方法
這篇文章主要介紹了android studio無法添加 bmob sdk依賴,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-05-05
android實(shí)現(xiàn)在圖標(biāo)上顯示數(shù)字
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)在圖標(biāo)上顯示數(shù)字,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下2022-04-04
Android layoutAnimation詳解及應(yīng)用
這篇文章主要介紹了Android layoutAnimation詳解及應(yīng)用的相關(guān)資料,需要的朋友可以參考下2017-05-05

