Flutter网络通讯插件zerotier_sockets的使用
Flutter网络通讯插件zerotier_sockets的使用
zerotier_sockets
是一个用于 Flutter 的插件,它提供了对 libzt
库的绑定。该库使用了 dart:ffi
。
支持的平台
目前仅支持 Android 平台。
要支持其他平台:
- 需要在对应的平台文件夹中创建默认内容。
- 将从
libzt
构建的库文件包含到相应的平台构建过程中。 - 修改
loader.dart
以包含新的平台。
使用方法
更多详细用法请参见 example
文件夹。也可以参考 ZeroTier Sockets 教程。
目前只实现了客户端TCP套接字。
import 'package:zerotier_sockets/zerotier_sockets.dart';
import 'package:path_provider/path_provider.dart';
Future<void> startNodeAndConnectToNetwork(String networkId) async {
// 获取节点实例
var node = ZeroTierNode.instance;
// 设置持久化存储路径,以便缓存身份和网络配置
// 你也可以使用 initSetIdentity 从内存设置身份,但网络配置不会被缓存
var appDocPath = (await getApplicationDocumentsDirectory()).path + '/zerotier_node';
node.initSetPath(appDocPath);
// 尝试启动节点
var result = node.start();
if (!result.success) {
throw Exception('Failed to start node: $result');
}
// 等待节点上线
await node.waitForOnline();
// 解析网络ID
var nwId = BigInt.parse(networkId, radix: 16);
// 加入网络
result = node.join(nwId);
if (!result.success) {
throw Exception('Failed to join network: $result');
}
// 等待网络准备就绪
await node.waitForNetworkReady(nwId);
await node.waitForAddressAssignment(nwId);
// 获取网络信息
var networkInfo = node.getNetworkInfo(nwId);
print(networkInfo.name);
print(networkInfo.address);
print(networkInfo.id);
ZeroTierSocket socket;
try {
// 连接套接字
socket = await ZeroTierSocket.connect('10.144.242.244', 22);
} catch (e) {
print('Failed to connect socket: $e');
socket.close();
return;
}
// 发送数据
socket.sink.add([1, 2, 3, 4, 5]);
// 监听数据
socket.stream.listen((data) => print('received ${data.length} byte(s)'));
// 检测套接字关闭
socket.done.then((_) => print('socket closed'));
// 不要忘记关闭套接字
socket.close();
}
事件
libzt
提供了一种方式,可以通过回调方法在后台线程中调用事件发生时的方法。
问题是 Dart 和 Flutter 不支持在隔离线程中调用。
解决方法包括:
- 分叉
libzt
并使用Dart_Port
,如这里所述。 - 等待 Dart 团队实现从后台线程调用。
- 可以使用平台通道,但这必须为每个平台单独实现,并且不是理想的选择。此外,Java API 传递的事件和数据有限。
在前两种情况下,libzt
必须使用 ZTS_C_API_ONLY
宏构建,因为不会使用 Java 回调。
示例代码
以下是完整的示例代码,展示了如何使用 zerotier_sockets
插件来启动节点并连接到网络。
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:zerotier_sockets/zerotier_sockets.dart';
import 'dialogs.dart';
import 'network_model.dart';
import 'socket_model.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
[@override](/user/override)
Widget build(BuildContext context) {
return const MaterialApp(
title: "ZeroTier Sockets",
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
[@override](/user/override)
HomePageState createState() => HomePageState();
}
class HomePageState extends State<HomePage> {
HomePageState() {
_node = ZeroTierNode.instance;
}
late final ZeroTierNode _node;
late final Timer _timer;
bool _startNode = false;
bool _allowStartNode = true;
bool _running = false;
bool _online = false;
String _identity = 'none';
final List<BigInt> _networkIds = [];
final List<NetworkModel> _networks = [];
final List<SocketModel> _sockets = [];
Future<void> _toggleStartNode(bool value) async {
setState(() => _allowStartNode = false);
_startNode = value;
if (value) {
_node.start();
} else {
_networkIds.clear();
_networks.clear();
_node.stop();
}
// 防止重复操作导致崩溃的小冷却时间
await Future.delayed(const Duration(milliseconds: 500));
setState(() => _allowStartNode = true);
}
Future<void> _onJoinNetwork() async {
var result = await InputDialog.show(context, title: 'Join network', hint: 'Network ID', sendText: 'ADD');
if (result != null) {
var networkId = BigInt.tryParse(result, radix: 16);
if (networkId != null) {
_networkIds.add(networkId);
_node.join(networkId);
}
}
}
Future<void> _onLeaveNetwork(BigInt networkId) async {
var result = await BoolDialog.show(context, title: 'Leave network?');
if (result == true) {
_networkIds.remove(networkId);
_node.leave(networkId);
}
}
Future<void> _onCreateSocket() async {
var address = await InputDialog.show(context, title: 'Connect socket', hint: 'IP address', sendText: 'NEXT');
if (address == null) {
return;
}
var port = await InputDialog.show(context, title: 'Connect socket', hint: 'Port', sendText: 'CONNECT');
if (port == null || int.tryParse(port) == null) {
return;
}
_sockets.add(SocketModel(address, int.parse(port)));
setState(() {});
}
Future<void> _onSocketPress(SocketModel socket) async {
await ActionDialog.show(
context,
title: 'Socket',
actions: [
ActionDialogAction(text: 'Reconnect', onPressed: socket.reconnect),
ActionDialogAction(text: 'Disconnect', onPressed: socket.disconnect),
ActionDialogAction(
text: 'Delete',
onPressed: () {
_sockets.remove(socket);
setState(() {});
},
),
],
);
}
[@override](/user/override)
void initState() {
super.initState();
_timer = Timer.periodic(const Duration(milliseconds: 500), (t) => _updateStatus());
}
[@override](/user/override)
void dispose() {
_timer.cancel();
super.dispose();
}
void _updateStatus() {
bool needUpdate = false;
if (_running != _node.running) {
_running = _node.running;
needUpdate = true;
}
if (_online != _node.online) {
_online = _node.online;
needUpdate = true;
}
final id = _node.getId();
if (id.success) {
var identity = id.data.toRadixString(16);
if (identity != _identity) {
_identity = identity ?? 'none';
needUpdate = true;
}
} else {
if (_identity != 'none') {
_identity = 'none';
needUpdate = true;
}
}
NetworkModel createNetworkModel(BigInt id) {
var info = _node.getNetworkInfo(id);
if (info != null) {
return NetworkModel.fromInfo(info);
} else {
return NetworkModel.fromId(id);
}
}
if (_networks.length != _networkIds.length) {
needUpdate = true;
_networks.clear();
for (var id in _networkIds) {
_networks.add(createNetworkModel(id));
}
} else {
for (var i = 0; i < _networkIds.length; i++) {
var model = createNetworkModel(_networkIds[i]);
if (!model.equals(_networks[i])) {
_networks[i] = model;
needUpdate = true;
}
}
}
if (needUpdate) {
setState(() {});
}
}
[@override](/user/override)
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: const Text('ZeroTier Sockets'),
),
body: Column(children: [
Expanded(
child: SingleChildScrollView(
child: Material(
color: Colors.transparent,
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
const SizedBox(height: 16),
SwitchListTile(
title: const Text('Start node'),
value: _startNode,
enableFeedback: true,
onChanged: _allowStartNode ? _toggleStartNode : null,
),
ListTile(
title: const Text('Status'),
trailing: Text(
_running ? (_online ? 'online' : 'connecting...') : 'not running',
),
),
ListTile(
title: const Text('Identity'),
trailing: Text(_identity),
),
...(!_running)
? []
: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Text('Networks', style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.start),
),
..._networks.map((model) {
return ListTile(
leading: const Icon(Icons.public),
minLeadingWidth: 20,
title: Text(model.name),
subtitle: Text('${model.id.toRadixString(16)}\r\n${model.address} • ${model.type}'),
trailing: Text(model.status),
onTap: () => _onLeaveNetwork(model.id),
);
}).toList(),
ListTile(
leading: const Icon(Icons.add),
minLeadingWidth: 20,
title: const Text('Join network'),
onTap: _onJoinNetwork,
),
Padding(
padding: const EdgeInsets.all(16.0),
child: Text('Socket instances', style: Theme.of(context).textTheme.titleLarge, textAlign: TextAlign.start),
),
..._sockets.map(
(e) => AnimatedBuilder(
animation: e,
builder: (context, child) => ListTile(
leading: const Icon(Icons.public),
minLeadingWidth: 20,
title: Text('${e.address}:${e.port}'),
subtitle: Text(e.status),
onTap: () => _onSocketPress(e),
),
),
),
ListTile(
leading: const Icon(Icons.add),
minLeadingWidth: 20,
title: const Text('Create socket'),
onTap: _onCreateSocket,
),
],
],
),
),
),
)
]),
),
);
}
}
更多关于Flutter网络通讯插件zerotier_sockets的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter网络通讯插件zerotier_sockets的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,以下是一个关于如何在Flutter项目中使用zerotier_sockets
插件进行网络通讯的示例代码。这个插件允许你通过ZeroTier网络进行套接字通信。
首先,确保你已经在pubspec.yaml
文件中添加了zerotier_sockets
依赖:
dependencies:
flutter:
sdk: flutter
zerotier_sockets: ^latest_version # 请替换为最新版本号
然后运行flutter pub get
来安装依赖。
接下来,我们将展示一个简单的示例,该示例将展示如何初始化ZeroTier套接字并进行基本的网络通信。
1. 初始化ZeroTier并获取网络状态
import 'package:flutter/material.dart';
import 'package:zerotier_sockets/zerotier_sockets.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
ZeroTierSockets? _zeroTierSockets;
String _status = 'Initializing...';
@override
void initState() {
super.initState();
_initZeroTier();
}
void _initZeroTier() async {
_zeroTierSockets = ZeroTierSockets();
_zeroTierSockets!.init().then((success) {
if (success) {
_zeroTierSockets!.getNetworkStatus().then((status) {
setState(() {
_status = 'ZeroTier Status: ${status.online ? 'Online' : 'Offline'}';
});
});
} else {
setState(() {
_status = 'Failed to initialize ZeroTier';
});
}
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('ZeroTier Flutter Example'),
),
body: Center(
child: Text(_status),
),
),
);
}
@override
void dispose() {
_zeroTierSockets?.dispose();
super.dispose();
}
}
2. 创建一个TCP客户端并连接到服务器
接下来,我们将展示如何使用zerotier_sockets
插件创建一个TCP客户端并连接到指定的服务器。
import 'dart:typed_data';
import 'dart:convert';
import 'package:flutter/material.dart';
import 'package:zerotier_sockets/zerotier_sockets.dart';
class TCPClientScreen extends StatefulWidget {
@override
_TCPClientScreenState createState() => _TCPClientScreenState();
}
class _TCPClientScreenState extends State<TCPClientScreen> {
ZeroTierSockets? _zeroTierSockets;
TCPSocket? _socket;
String _response = '';
TextEditingController _controller = TextEditingController();
@override
void initState() {
super.initState();
_zeroTierSockets = ZeroTierSockets();
_connectToServer();
}
void _connectToServer() async {
String serverAddress = '192.168.xxx.xxx'; // 替换为你的ZeroTier网络中的服务器地址
int port = 12345; // 替换为你的服务器端口号
_socket = await _zeroTierSockets!.createTCPSocket();
_socket!.connect(serverAddress, port).then((success) {
if (success) {
_socket!.listen((Uint8List data) {
setState(() {
_response += String.fromCharCodes(data);
});
});
} else {
setState(() {
_response = 'Failed to connect to server';
});
}
});
}
void _sendMessage() async {
if (_socket!.isOpen) {
String message = _controller.text;
Uint8List data = Uint8List.fromList(utf8.encode(message));
_socket!.write(data);
_controller.clear();
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('TCP Client'),
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text('Server Response:', style: TextStyle(fontSize: 18)),
SizedBox(height: 16),
Expanded(
child: SingleChildScrollView(
child: Text(_response, style: TextStyle(fontSize: 16)),
),
),
SizedBox(height: 16),
TextField(
controller: _controller,
decoration: InputDecoration(
border: OutlineInputBorder(),
labelText: 'Message',
),
),
SizedBox(height: 16),
ElevatedButton(
onPressed: _sendMessage,
child: Text('Send'),
),
],
),
),
);
}
@override
void dispose() {
_socket?.close();
_zeroTierSockets?.dispose();
_controller.dispose();
super.dispose();
}
}
3. 使用TCP客户端屏幕
现在,你可以将TCP客户端屏幕添加到你的应用程序中:
class MyApp extends StatefulWidget {
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
appBar: AppBar(
title: Text('ZeroTier Flutter Example'),
),
body: TCPClientScreen(), // 使用TCP客户端屏幕
),
);
}
}
这个示例展示了如何使用zerotier_sockets
插件在Flutter应用程序中进行基本的网络通讯。请根据你的实际需求调整服务器地址和端口号。