Flutter近场服务发现插件nsd的使用

发布于 1周前 作者 gougou168 来自 Flutter

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

1 回复

更多关于Flutter近场服务发现插件nsd的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,下面是一个关于如何在Flutter中使用近场服务发现(NSD, Nearby Service Discovery)插件的示例代码。这个示例展示了如何设置NSD服务,以及如何发现和连接其他NSD服务。

首先,你需要确保你的Flutter项目已经添加了NSD插件。假设你使用的是nearby_connections插件,它提供了NSD的基本功能。

  1. 添加依赖

在你的pubspec.yaml文件中添加以下依赖:

dependencies:
  flutter:
    sdk: flutter
  nearby_connections: ^x.y.z  # 替换为最新版本号

然后运行flutter pub get来安装依赖。

  1. 设置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'),
      ),
    );
  }
}
  1. 发现和连接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插件进行服务发现和连接。你可以根据需求进一步扩展和优化这些代码。

回到顶部