如何基于Flutter和Dart开发跨路由器连接的离线聊天应用?
Flutter离线跨路由器聊天应用实现方案
核心前提:打通跨路由器网络
要实现两台路由器下的用户离线通信,首先得打破两个局域网的隔离,最可靠的方式是在两台路由器之间建立站点到站点VPN:
- 推荐用WireGuard(轻量、高性能)或OpenVPN,配置两台路由器互相作为Peer,允许各自局域网段的IP访问对方网络。配置完成后,两个路由器下的设备就像处于同一内网,可直接通过内网IP通信。
Flutter端通信架构
1. 设备发现
用mDNS(多播DNS)自动发现同一内网(含VPN打通的远端局域网)的在线设备,无需用户手动输入IP:
- 可使用
flutter_mdns库,扫描局域网内的服务实例,获取设备的IP和端口。
2. 点对点通信方案
文本消息:TCP可靠传输
文本消息要求不丢包、顺序正确,用TCP协议实现:
- 基于Dart原生
dart:io的Socket类,建立客户端与服务端的连接。 - 消息封装为JSON格式,示例:
{"type":"text","sender":"user1","content":"Hello","timestamp":1699999999} - 维护连接池,每个在线设备对应一个Socket连接,用心跳包(比如每30秒发一次空消息)检测连接状态,断开后自动重连。
文件/视频:分片传输
大文件直接传输易导致内存溢出或中断,采用分片+校验的方式:
- 分片大小设为1MB左右,发送端先传输文件元数据(文件名、大小、哈希值),再逐个发送分片。
- 接收端接收分片后校验哈希,全部接收完成后重组文件。
- 用Dart的
dart:io操作文件读写,避免阻塞UI线程,建议用Isolate处理文件IO。
3. 代码示例
TCP服务端监听
import 'dart:io'; import 'dart:convert'; void startChatServer(int port) async { final server = await ServerSocket.bind(InternetAddress.anyIPv4, port); print("Chat server running on ${server.address}:${server.port}"); await for (final socket in server) { _handleClientConnection(socket); } } void _handleClientConnection(Socket socket) { print("New client connected: ${socket.remoteAddress}:${socket.remotePort}"); socket.listen( (data) { final message = utf8.decode(data); print("Received: $message"); // 可将消息广播给其他连接的客户端 }, onError: (err) => print("Connection error: $err"), onDone: () { print("Client disconnected"); socket.close(); }, ); }
TCP客户端连接与消息发送
import 'dart:io'; import 'dart:convert'; class ChatClient { Socket? _socket; Future<void> connect(String ip, int port) async { try { _socket = await Socket.connect(ip, port); print("Connected to server"); _socket?.listen( (data) => print("Received: ${utf8.decode(data)}"), onError: (err) => print("Connection error: $err"), onDone: () => print("Disconnected from server"), ); } catch (e) { print("Failed to connect: $e"); } } void sendMessage(String content) { final message = jsonEncode({ "type": "text", "content": content, "timestamp": DateTime.now().millisecondsSinceEpoch }); _socket?.write(message); } void disconnect() => _socket?.close(); }
文件分片发送示例
import 'dart:io'; Future<void> sendFile(Socket socket, String filePath) async { final file = File(filePath); final fileSize = await file.length(); const chunkSize = 1024 * 1024; // 1MB分片 // 发送文件元数据 socket.write("FILE_META:${file.path.split('/').last}:$fileSize\n"); final raf = await file.open(); int sentBytes = 0; while (sentBytes < fileSize) { final remaining = fileSize - sentBytes; final currentChunkSize = remaining > chunkSize ? chunkSize : remaining; final chunk = await raf.read(currentChunkSize); socket.add(chunk); sentBytes += currentChunkSize; // 此处可回调更新UI传输进度 print("Sent ${(sentBytes / fileSize * 100).toStringAsFixed(1)}%"); } await raf.close(); socket.write("FILE_END\n"); print("File sent complete"); }
权限与兼容性
- Android:需添加
INTERNET、READ_EXTERNAL_STORAGE/READ_MEDIA_VIDEO(Android 13+)、WRITE_EXTERNAL_STORAGE权限。 - iOS:需开启网络权限,添加
NSFileProviderDomainUsageDescription用于文件访问。
状态管理与UI
- 用Riverpod或Provider管理在线用户列表、消息记录、文件传输进度等状态。
- UI层基于
ListView展示聊天消息,用LinearProgressIndicator显示文件传输进度。
内容的提问来源于stack exchange,提问作者Rabia Usman
相关产品推荐
相关产品推荐

