Flutter中實(shí)現(xiàn)TCP通信的關(guān)鍵步驟與代碼示例
引言
在移動(dòng)端開(kāi)發(fā)中,除了常見(jiàn)的 HTTP、MQTT 之外,很多場(chǎng)景需要直接使用 TCP 通信,例如局域網(wǎng)設(shè)備控制、實(shí)時(shí)傳輸?shù)?。本文將介紹在 Flutter/Dart 中實(shí)現(xiàn)一個(gè) TCP 客戶端的基本過(guò)程,并解析關(guān)鍵代碼點(diǎn)。文章同時(shí)給出 自動(dòng)重連 與 心跳保活 的完整示例代碼,便于直接落地。
1. 基本思路
- 使用
Socket.connect建立連接 - 將
socket轉(zhuǎn)換為 流(Stream) 進(jìn)行監(jiān)聽(tīng),實(shí)時(shí)接收消息 - 使用
LineSplitter按行切分消息,避免 TCP 粘包/分包問(wèn)題(前提:每條消息以\n結(jié)尾,且內(nèi)容不含換行) - 加入 超時(shí)/心跳 與 自動(dòng)重連(指數(shù)退避 + 抖動(dòng))
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. 心跳與空閑超時(shí)
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. 自動(dòng)重連(指數(shù)退避 + 抖動(dòng))
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) → 重連
- 自動(dòng)重連:指數(shù)退避 + 抖動(dòng)
- 超時(shí)治理:連接超時(shí)、請(qǐng)求超時(shí)、空閑超時(shí)
- 可觀測(cè)性:埋點(diǎn)連接時(shí)延、失敗原因、重連次數(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通信的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
Android Jetpack架構(gòu)中ViewModel接口暴露的不合理探究
這篇文章主要介紹了Android Jetpack架構(gòu)組件 ViewModel詳解,ViewModel類(lèi)讓數(shù)據(jù)可在發(fā)生屏幕旋轉(zhuǎn)等配置更改后繼續(xù)存在,ViewModel類(lèi)旨在以注重生命周期的方式存儲(chǔ)和管理界面相關(guān)的數(shù)據(jù)。感興趣可以來(lái)學(xué)習(xí)一下2022-07-07
分享40條Android開(kāi)發(fā)的優(yōu)化建議
這篇文章主要為大家詳細(xì)介紹了40條Android開(kāi)發(fā)的優(yōu)化建議,幫助大家更好的開(kāi)發(fā)Android項(xiàng)目,感興趣的小伙伴們可以參考一下2016-08-08
Android項(xiàng)目開(kāi)發(fā)之UI設(shè)計(jì)器
這篇文章主要為大家詳細(xì)介紹了Android項(xiàng)目開(kāi)發(fā)之UI設(shè)計(jì)器,具有一定的實(shí)用性和參考價(jià)值,感興趣的小伙伴們可以參考一下2016-06-06
Android中利用C++處理Bitmap對(duì)象的實(shí)現(xiàn)方法
下面小編就為大家?guī)?lái)一篇Android中利用C++處理Bitmap對(duì)象的實(shí)現(xiàn)方法。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2017-03-03
Art 虛擬機(jī)系列Heap內(nèi)存模型分配策略詳解
這篇文章主要為大家介紹了Art 虛擬機(jī)系列Heap內(nèi)存模型分配策略詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-03-03
Android跟隨手指移動(dòng)的控件demo實(shí)例
大家好,本篇文章主要講的是Android跟隨手指移動(dòng)的控件demo實(shí)例,感興趣的同學(xué)趕快來(lái)看一看吧,對(duì)你有幫助的話記得收藏一下,方便下次瀏覽2021-12-12
Kotlin + Flow 實(shí)現(xiàn)Android 應(yīng)用初始化任務(wù)啟動(dòng)庫(kù)
這篇文章主要介紹了Kotlin + Flow 實(shí)現(xiàn)Android 應(yīng)用初始化任務(wù)啟動(dòng)庫(kù)的方法,幫助大家更好的理解和學(xué)習(xí)使用Android,感興趣的朋友可以了解下2021-03-03
Android實(shí)現(xiàn)關(guān)機(jī)后數(shù)據(jù)不會(huì)丟失問(wèn)題
這篇文章主要介紹了Android實(shí)現(xiàn)關(guān)機(jī)后數(shù)據(jù)不會(huì)丟失問(wèn)題,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2019-10-10
Android中ListView + CheckBox實(shí)現(xiàn)單選、多選效果
這篇文章主要介紹了Android中ListView + CheckBox實(shí)現(xiàn)單選、多選效果,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-02-02
android實(shí)現(xiàn)滑動(dòng)解鎖
這篇文章主要為大家詳細(xì)介紹了android實(shí)現(xiàn)滑動(dòng)解鎖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-04-04

