1. 插件介绍

1.1 功能概述

Multicast DNS 是一个 Dart 包,用于通过 mDNS(Multicast DNS)协议进行服务发现,支持 Bonjour 和 Avahi 等服务发现技术。该包允许 Flutter 应用程序在本地网络中发现和解析其他设备或服务,无需依赖传统的 DNS 服务器。

1.2 主要特性

  • 支持 IPv4 和 IPv6 网络接口
  • 实现 RFC 6762 中定义的 “One-Shot Multicast DNS Queries” 规范
  • 支持多种资源记录类型查询
  • 内置资源记录缓存机制
  • 可配置的网络接口选择

1.3 鸿蒙平台支持

该自定义修改版本的 Multicast DNS 包已适配鸿蒙平台,支持在鸿蒙设备上进行 mDNS 服务发现和查询。

2. 安装与配置

2.1 Git 依赖引入

由于这是一个自定义修改版本的包,需要通过 Git 形式引入。在项目的 pubspec.yaml 文件中添加以下依赖:

dependencies:
  multicast_dns:
    git:
      url: "https://gitcode.com/openharmony-tpc/flutter_packages.git"
      path: "packages/multicast_dns"

2.2 鸿蒙平台权限配置

在鸿蒙平台上使用网络相关功能,需要配置相应的网络权限。修改 module.json5 文件:

{
  "module": {
    "name": "entry",
    "type": "entry",
    "description": "Flutter Multicast DNS Example",
    "mainElement": "MainAbility",
    "deviceTypes": ["phone", "tablet"],
    "reqPermissions": [
      {
        "name": "ohos.permission.INTERNET",
        "reason": "需要网络权限进行 mDNS 查询",
        "usedScene": {
          "abilities": ["MainAbility"],
          "when": "inuse"
        }
      },
      {
        "name": "ohos.permission.GET_WIFI_INFO",
        "reason": "需要 WiFi 信息进行网络接口选择",
        "usedScene": {
          "abilities": ["MainAbility"],
          "when": "inuse"
        }
      }
    ],
    // ... 其他配置
  }
}

3. API 调用示例

3.1 核心类介绍

MDnsClient

MDnsClient 是该包的核心类,用于执行 mDNS 查询操作。主要方法包括:

  • start(): 启动 mDNS 客户端,开始监听和发送查询
  • stop(): 停止客户端,释放资源
  • lookup<T>(): 执行特定类型的资源记录查询
ResourceRecordQuery

用于创建不同类型的资源记录查询,主要方法包括:

  • ResourceRecordQuery.serverPointer(name): 创建 PTR 记录查询
  • ResourceRecordQuery.service(name): 创建 SRV 记录查询
  • ResourceRecordQuery.addressIPv4(name): 创建 A 记录查询(IPv4 地址)
  • ResourceRecordQuery.addressIPv6(name): 创建 AAAA 记录查询(IPv6 地址)

3.2 基本使用流程

下面是一个完整的使用示例,展示如何使用 Multicast DNS 包发现本地网络中的 Dart Observatory 服务:

import 'package:multicast_dns/multicast_dns.dart';

Future<void> discoverDartObservatory() async {
  // 创建 MDnsClient 实例
  final MDnsClient client = MDnsClient();
  
  try {
    // 启动客户端,默认监听所有 IPv4 网络接口
    await client.start();
    
    const String serviceName = '_dartobservatory._tcp.local';
    
    // 1. 查询 PTR 记录获取服务实例
    await for (final PtrResourceRecord ptr in client
        .lookup<PtrResourceRecord>(ResourceRecordQuery.serverPointer(serviceName))) {
      print('发现服务实例: ${ptr.domainName}');
      
      // 2. 查询 SRV 记录获取服务端口和目标主机
      await for (final SrvResourceRecord srv in client.lookup<SrvResourceRecord>(
          ResourceRecordQuery.service(ptr.domainName))) {
        print('服务地址: ${srv.target}:${srv.port}');
        
        // 3. 查询 A 记录获取 IPv4 地址
        await for (final IPAddressResourceRecord ipAddr in client
            .lookup<IPAddressResourceRecord>(ResourceRecordQuery.addressIPv4(srv.target))) {
          print('IPv4 地址: ${ipAddr.address}');
        }
      }
    }
  } catch (e) {
    print('服务发现失败: $e');
  } finally {
    // 停止客户端,释放资源
    client.stop();
  }
}

3.3 高级配置选项

指定网络接口类型

可以通过 start() 方法的参数指定监听的网络接口类型:

// 只监听 IPv6 网络接口
await client.start(
  listenAddress: InternetAddress.anyIPv6,
);
自定义网络接口工厂

可以自定义网络接口工厂来选择特定的网络接口:

Future<Iterable<NetworkInterface>> customInterfacesFactory(InternetAddressType type) async {
  // 获取所有网络接口
  final List<NetworkInterface> interfaces = await NetworkInterface.list(
    includeLinkLocal: true,
    type: type,
    includeLoopback: false, // 排除回环接口
  );
  
  // 只返回 WiFi 接口
  return interfaces.where((interface) => interface.name.contains('wlan'));
}

// 使用自定义网络接口工厂
await client.start(
  interfacesFactory: customInterfacesFactory,
);
配置查询超时

可以为查询设置超时时间:

await for (final PtrResourceRecord ptr in client
    .lookup<PtrResourceRecord>(
      ResourceRecordQuery.serverPointer(serviceName),
      timeout: Duration(seconds: 10), // 设置 10 秒超时
    )) {
  // 处理查询结果
}

4. 完整示例应用

下面是一个完整的鸿蒙 Flutter 应用示例,展示如何使用 Multicast DNS 包:

import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:multicast_dns/multicast_dns.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'mDNS 服务发现',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: const MyHomePage(title: 'mDNS 服务发现示例'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({super.key, required this.title});

  final String title;

  
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  final List<String> _services = [];
  bool _isScanning = false;

  Future<void> _scanForServices() async {
    setState(() {
      _isScanning = true;
      _services.clear();
    });

    final MDnsClient client = MDnsClient();
    
    try {
      await client.start();
      
      // 查找所有 HTTP 服务
      const String serviceName = '_http._tcp.local';
      
      await for (final PtrResourceRecord ptr in client
          .lookup<PtrResourceRecord>(ResourceRecordQuery.serverPointer(serviceName))) {
        _services.add('发现服务: ${ptr.domainName}');
        
        await for (final SrvResourceRecord srv in client.lookup<SrvResourceRecord>(
            ResourceRecordQuery.service(ptr.domainName))) {
          _services.add('  地址: ${srv.target}:${srv.port}');
          
          // 查询 IPv4 地址
          await for (final IPAddressResourceRecord ipAddr in client
              .lookup<IPAddressResourceRecord>(ResourceRecordQuery.addressIPv4(srv.target))) {
            _services.add('  IPv4: ${ipAddr.address}');
          }
        }
      }
      
      if (_services.isEmpty) {
        _services.add('未发现任何 HTTP 服务');
      }
    } catch (e) {
      _services.add('扫描失败: $e');
    } finally {
      client.stop();
      setState(() {
        _isScanning = false;
      });
    }
  }

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            ElevatedButton(
              onPressed: _isScanning ? null : _scanForServices,
              child: _isScanning 
                  ? const CircularProgressIndicator() 
                  : const Text('扫描本地 HTTP 服务'),
            ),
            const SizedBox(height: 20),
            Expanded(
              child: ListView.builder(
                itemCount: _services.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(_services[index]),
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

5. 鸿蒙平台注意事项

5.1 网络权限

确保在 module.json5 文件中配置了必要的网络权限,否则应用可能无法正常进行 mDNS 查询。

5.2 网络环境

mDNS 协议依赖于本地网络的多播支持,请确保设备连接到支持多播的网络环境(通常是 WiFi 网络)。

5.3 性能考虑

  • 避免频繁创建和销毁 MDnsClient 实例,建议在应用生命周期内复用同一个实例
  • 及时调用 stop() 方法释放资源,避免内存泄漏
  • 对于长时间运行的应用,可以定期清理资源记录缓存

5.4 兼容性

  • 支持鸿蒙 API 9 及以上版本
  • 兼容 Flutter 3.7.0 及以上版本
  • 兼容 Dart 2.19.0 及以上版本

6. 总结

Flutter Multicast DNS 包为鸿蒙平台提供了强大的服务发现能力,允许应用程序在本地网络中发现和解析其他设备或服务。通过本文的介绍,您应该已经了解了如何:

  1. 以 Git 形式引入自定义修改版本的 Multicast DNS 包
  2. 配置鸿蒙平台所需的网络权限
  3. 使用 MDnsClient 类执行服务发现查询
  4. 处理不同类型的资源记录
  5. 实现完整的服务发现功能

该包在智能家居、本地网络应用、设备配对等场景中具有广泛的应用前景,能够帮助开发者构建更加智能和互联的鸿蒙应用程序。

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐