Flutter近场服务发现插件nsd的使用
Flutter近场服务发现插件nsd的使用
nsd
是一个用于网络服务发现和注册(NSD / DNS-SD / Bonjour / mDNS)的Flutter插件。以下是其基本用法、权限配置、示例应用以及高级用法的详细介绍。
基本用法
服务发现
要开始发现特定类型的服务,可以使用以下代码:
import 'package:nsd/nsd.dart';
final discovery = await startDiscovery('_http._tcp');
discovery.addListener(() {
// discovery.services 包含已发现的服务
});
// ...
await stopDiscovery(discovery);
或者,您也可以使用 addServiceListener
方法来监听服务状态变化:
discovery.addServiceListener((service, status) {
if (status == ServiceStatus.found) {
// 将服务添加到自己的集合中
}
});
服务注册
要注册一个服务,可以使用以下代码:
import 'package:nsd/nsd.dart';
final registration = await register(const Service(name: 'Foo', type: '_http._tcp', port: 56000));
// ...
await unregister(registration);
权限配置
Android
在 AndroidManifest.xml
中添加以下权限:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />
iOS
在 Info.plist
中添加以下权限:
<key>NSLocalNetworkUsageDescription</key>
<string>Required to discover local network devices</string>
<key>NSBonjourServices</key>
<array>
<string>_http._tcp</string>
</array>
示例应用
插件包含一个示例应用,展示了如何启动多个发现和注册多个服务。该应用还可以发现其自身服务以及其他类型的本地网络服务(如打印机)。以下是示例应用的完整代码:
import 'dart:async';
import 'dart:convert';
import 'dart:typed_data';
import 'package:flutter/material.dart';
import 'package:flutter_speed_dial/flutter_speed_dial.dart';
import 'package:nsd/nsd.dart';
import 'package:provider/provider.dart';
const String serviceTypeDiscover = '_http._tcp';
const String serviceTypeRegister = '_http._tcp';
const utf8encoder = Utf8Encoder();
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State createState() => MyAppState();
}
class MyAppState extends State<MyApp> {
final discoveries = <Discovery>[];
final registrations = <Registration>[];
var _nextPort = 56360;
int get nextPort => _nextPort++; // TODO ensure ports are not taken
MyAppState() {
enableLogging(LogTopic.calls);
}
Future<void> addDiscovery() async {
final discovery = await startDiscovery(serviceTypeDiscover);
setState(() {
discoveries.add(discovery);
});
}
Future<void> dismissDiscovery(Discovery discovery) async {
setState(() {
discoveries.remove(discovery);
});
await stopDiscovery(discovery);
}
Future<void> addRegistration() async {
final service = Service(
name: 'Some Service',
type: serviceTypeRegister,
port: nextPort,
txt: createTxt());
final registration = await register(service);
setState(() {
registrations.add(registration);
});
}
Future<void> dismissRegistration(Registration registration) async {
setState(() {
registrations.remove(registration);
});
await unregister(registration);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
home: Scaffold(
floatingActionButtonLocation: FloatingActionButtonLocation.centerFloat,
floatingActionButton: SpeedDial(
icon: Icons.add,
spacing: 10,
spaceBetweenChildren: 5,
children: [
SpeedDialChild(
elevation: 2,
child: const Icon(Icons.wifi_tethering),
label: 'Register Service',
onTap: () async => addRegistration(),
),
SpeedDialChild(
elevation: 2,
child: const Icon(Icons.wifi_outlined),
label: 'Start Discovery',
onTap: () async => addDiscovery(),
),
],
),
body: Column(
children: <Widget>[
Expanded(
child: ListView.builder(
controller: ScrollController(),
itemBuilder: buildDiscovery,
itemCount: discoveries.length,
),
),
const Divider(
height: 20,
thickness: 4,
indent: 0,
endIndent: 0,
color: Colors.blue,
),
Expanded(
child: ListView.builder(
controller: ScrollController(),
itemBuilder: buildRegistration,
itemCount: registrations.length,
),
),
],
),
),
);
}
Widget buildDiscovery(context, index) {
final discovery = discoveries.elementAt(index);
return Dismissible(
key: ValueKey(discovery.id),
onDismissed: (_) async => dismissDiscovery(discovery),
child: DiscoveryWidget(discovery));
}
Widget buildRegistration(context, index) {
final registration = registrations.elementAt(index);
return Dismissible(
key: ValueKey(registration.id),
onDismissed: (_) async => dismissRegistration(registration),
child: RegistrationWidget(registration));
}
}
class DiscoveryWidget extends StatefulWidget {
final Discovery discovery;
DiscoveryWidget(this.discovery) : super(key: ValueKey(discovery.id));
@override
State createState() => DiscoveryState();
}
class DiscoveryState extends State<DiscoveryWidget> {
@override
Widget build(BuildContext context) {
return Card(
margin: const EdgeInsets.fromLTRB(16, 4, 16, 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ListTile(
leading: const Icon(Icons.wifi_outlined),
title: Text('Discovery ${shorten(widget.discovery.id)}')),
Padding(
padding: const EdgeInsets.fromLTRB(24, 0, 24, 0),
child: ChangeNotifierProvider.value(
value: widget.discovery,
child: Consumer<Discovery>(builder: buildDataTable),
)),
const SizedBox(
height: 16,
),
],
),
);
}
Widget buildDataTable(
BuildContext context, Discovery discovery, Widget? child) {
return DataTable(
headingRowHeight: 24,
dataRowMinHeight: 24,
dataRowMaxHeight: 24,
dataTextStyle: const TextStyle(color: Colors.black, fontSize: 12),
columnSpacing: 8,
horizontalMargin: 0,
headingTextStyle: const TextStyle(
color: Colors.black, fontSize: 12, fontWeight: FontWeight.w600),
columns: <DataColumn>[
buildDataColumn('Name'),
buildDataColumn('Type'),
buildDataColumn('Host'),
buildDataColumn('Port'),
],
rows: buildDataRows(discovery),
);
}
DataColumn buildDataColumn(String name) {
return DataColumn(
label: Text(
name,
),
);
}
List<DataRow> buildDataRows(Discovery discovery) {
return discovery.services
.map((e) => DataRow(
cells: <DataCell>[
DataCell(Text(e.name ?? 'unknown')),
DataCell(Text(e.type ?? 'unknown')),
DataCell(Text(e.host ?? 'unknown')),
DataCell(Text(e.port != null ? '${e.port}' : 'unknown'))
],
))
.toList();
}
}
class RegistrationWidget extends StatelessWidget {
final Registration registration;
RegistrationWidget(this.registration) : super(key: ValueKey(registration.id));
@override
Widget build(BuildContext context) {
final service = registration.service;
return Card(
margin: const EdgeInsets.fromLTRB(16, 4, 16, 4),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: <Widget>[
ListTile(
leading: const Icon(Icons.wifi_tethering),
title: Text('Registration ${shorten(registration.id)}'),
subtitle: Text(
'Name: ${service.name} ▪️ '
'Type: ${service.type} ▪️ '
'Host: ${service.host} ▪️ '
'Port: ${service.port}',
style: const TextStyle(color: Colors.black, fontSize: 12),
),
),
const SizedBox(
height: 8,
),
],
),
);
}
}
/// Shortens the id for display on-screen.
String shorten(String? id) {
return id?.toString().substring(0, 4) ?? 'unknown';
}
/// Creates a txt attribute object that showcases the most common use cases.
Map<String, Uint8List?> createTxt() {
return <String, Uint8List?>{
'a-string': utf8encoder.convert('κόσμε'),
'a-blank': Uint8List(0),
'a-null': null,
};
}
高级用法
获取服务的IP地址
如果您确实需要获取服务的IP地址,可以在启动发现时配置自动IP查找:
final discovery = await startDiscovery(serviceType, ipLookupType: IpLookupType.any);
每个已发现的服务将包含一个IP地址列表。
发现本地网络中的所有服务类型
要发现本地网络中的所有服务类型,可以使用以下代码:
final discovery = await startDiscovery('_services._dns-sd._udp', autoResolve: false);
禁用服务名称验证
如果您需要禁用服务名称验证,可以使用以下代码:
disableServiceTypeValidation(true);
启用调试日志
为了帮助调试,您可以启用特定主题的日志记录:
enableLogging(LogTopic.errors);
enableLogging(LogTopic.calls);
这将记录错误和所有调用到原生侧(及其回调),通常会提供有用的信息。
通过以上内容,您可以全面了解并使用 nsd
插件进行网络服务发现和注册。希望这些信息对您有所帮助!
更多关于Flutter近场服务发现插件nsd的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter近场服务发现插件nsd的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
当然,下面是一个关于如何在Flutter中使用近场服务发现(NSD, Nearby Service Discovery)插件的示例代码。这个示例展示了如何设置NSD服务,以及如何发现和连接其他NSD服务。
首先,你需要确保你的Flutter项目已经添加了NSD插件。假设你使用的是nearby_connections
插件,它提供了NSD的基本功能。
- 添加依赖
在你的pubspec.yaml
文件中添加以下依赖:
dependencies:
flutter:
sdk: flutter
nearby_connections: ^x.y.z # 替换为最新版本号
然后运行flutter pub get
来安装依赖。
- 设置NSD服务
在你的Flutter应用中,你需要启动NSD服务并等待其他设备发现你。以下是一个简单的示例代码:
import 'package:flutter/material.dart';
import 'package:nearby_connections/nearby_connections.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: NSDServicePage(),
);
}
}
class NSDServicePage extends StatefulWidget {
@override
_NSDServicePageState createState() => _NSDServicePageState();
}
class _NSDServicePageState extends State<NSDServicePage> {
late NearbyConnections _nearbyConnections;
late String _serviceName = "MyNSDService";
late String _endpointId = UUID.randomUUID().toString();
@override
void initState() {
super.initState();
_nearbyConnections = NearbyConnections(
onConnectionInitiated: _onConnectionInitiated,
onConnectionResult: _onConnectionResult,
onDisconnected: _onDisconnected,
onReceivedData: _onReceivedData,
);
_startAdvertising();
}
void _startAdvertising() async {
try {
await _nearbyConnections.startAdvertising(_endpointId, _serviceName,
strategy: ConnectionStrategy.P2P_CLUSTER);
print("Started advertising NSD service: $_serviceName");
} catch (e) {
print("Failed to start advertising NSD service: $e");
}
}
void _onConnectionInitiated(String endpointId, ConnectionInfo connectionInfo) {
print("Connection initiated from: $endpointId");
_nearbyConnections.acceptConnection(endpointId);
}
void _onConnectionResult(String endpointId, ConnectionResolution connectionResolution) {
if (connectionResolution.status == ConnectionResolution.Status.SUCCESS) {
print("Successfully connected to: $endpointId");
} else {
print("Failed to connect to: $endpointId");
}
}
void _onDisconnected(String endpointId) {
print("Disconnected from: $endpointId");
}
void _onReceivedData(String endpointId, Payload payload) {
print("Received data from: $endpointId, data: ${payload.content}");
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('NSD Service Example'),
),
body: Center(
child: Text('Advertising NSD service: $_serviceName'),
),
);
}
}
- 发现和连接NSD服务
在你的另一个Flutter设备或模拟器上,你可以使用以下代码来发现和连接NSD服务:
import 'package:flutter/material.dart';
import 'package:nearby_connections/nearby_connections.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: NSDDiscoveryPage(),
);
}
}
class NSDDiscoveryPage extends StatefulWidget {
@override
_NSDDiscoveryPageState createState() => _NSDDiscoveryPageState();
}
class _NSDDiscoveryPageState extends State<NSDDiscoveryPage> {
late NearbyConnections _nearbyConnections;
late String _serviceNameToDiscover = "MyNSDService";
late String _endpointId = UUID.randomUUID().toString();
@override
void initState() {
super.initState();
_nearbyConnections = NearbyConnections(
onConnectionInitiated: _onConnectionInitiated,
onConnectionResult: _onConnectionResult,
onDisconnected: _onDisconnected,
onReceivedData: _onReceivedData,
);
_startDiscovery();
}
void _startDiscovery() async {
try {
await _nearbyConnections.startDiscovery(_serviceNameToDiscover,
strategy: ConnectionStrategy.P2P_CLUSTER);
print("Started discovering NSD service: $_serviceNameToDiscover");
} catch (e) {
print("Failed to start discovering NSD service: $e");
}
}
void _onConnectionInitiated(String endpointId, ConnectionInfo connectionInfo) {
print("Connection initiated to: $endpointId");
_nearbyConnections.acceptConnection(endpointId);
}
void _onConnectionResult(String endpointId, ConnectionResolution connectionResolution) {
if (connectionResolution.status == ConnectionResolution.Status.SUCCESS) {
print("Successfully connected to: $endpointId");
} else {
print("Failed to connect to: $endpointId");
}
}
void _onDisconnected(String endpointId) {
print("Disconnected from: $endpointId");
}
void _onReceivedData(String endpointId, Payload payload) {
print("Received data from: $endpointId, data: ${payload.content}");
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('NSD Discovery Example'),
),
body: Center(
child: Text('Discovering NSD service: $_serviceNameToDiscover'),
),
);
}
}
注意:
UUID.randomUUID().toString()
用于生成唯一的端点ID。在实际应用中,你可能需要根据你的需求来生成或管理这些ID。ConnectionStrategy.P2P_CLUSTER
是连接策略之一,你可以根据需求选择其他策略。nearby_connections
插件的实际API可能会有所不同,请查阅最新的官方文档以确保正确性。
这个示例展示了如何在Flutter中使用NSD插件进行服务发现和连接。你可以根据需求进一步扩展和优化这些代码。