Flutter零配置连接插件zeroconnect的使用

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

Flutter零配置连接插件zeroconnect的使用

zeroconnect

使用NSD自动在局域网内创建数据流。

这是将Erhannis/zeroconnect 转换为Flutter版本的结果。某些语句或代码可能稍有不准确或奇怪,因为这是在Flutter环境中实现的。 请注意,我在尝试让不同的NSD/MDNS/Zeroconf等实现接受相同的类型结构或协同工作时遇到了一些问题,因此目前此版本无法与Python版本通信,尽管感觉已经接近了。

该插件使用了NSD,这意味着它有一些需求:

目录

权限

Android

在您的manifest文件中添加以下权限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.CHANGE_WIFI_MULTICAST_STATE" />

iOS

在您的Info.plist文件中添加以下权限,并相应地替换"YOURSERVICEID":

<key>NSLocalNetworkUsageDescription</key>
<string>Required to discover local network devices</string>
<key>NSBonjourServices</key>
<array>
    <string>_YOURSERVICEID._tcp</string>
</array>

用法

一个或多个服务器和一个或多个客户端运行在同一局域网上。(可以是Wi-Fi或以太网)

最基本的用法

服务端

import 'package:zeroconnect/zeroconnect.dart';

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized(); // 如果已经调用了`runApp`,则不需要这行
  await ZeroConnect().advertise(serviceId: "YOURSERVICEID", callback: (messageSock, nodeId, serviceId) async {
    print("got message connection from $nodeId");
    var str = await messageSock.recvString();
    print(str);
    await messageSock.sendString("Hello from server");
  });
}

客户端

import 'package:zeroconnect/zeroconnect.dart';

Future<void> main() async {
  var messageSock = await ZeroConnect().connectToFirst(serviceId: "YOURSERVICEID");
  await messageSock?.sendString("Hello from client");
  var str = await messageSock?.recvBytes();
  print(str);
}

较复杂的用法

服务端

import 'package:zeroconnect/zeroconnect.dart';

const SERVICE_ID = "YOURSERVICEID";

Future<void> main() async {
  WidgetsFlutterBinding.ensureInitialized(); // 如果已经调用了`runApp`,则不需要这行
  var zc = ZeroConnect(localId: "SERVER_ID");
  await zc.advertise(serviceId: SERVICE_ID, callback: (messageSock, nodeId, serviceId) async {
    print("got message connection from $nodeId");
    // 如果你也想自发发送消息,可以将socket传递给其他线程
    while (true) {
      var str = await messageSock.recvString();
      print("$str");
      switch (str) {
        case "enable jimjabber":
          print("ENABLE JIMJABBER");
          break;
        case "save msg:":
          var toSave = await messageSock.recvBytes();
          print("SAVE MESSAGE $toSave");
          break;
        case "marco":
          await messageSock.sendString("polo");
          print("PING PONGED");
          break;
        case null:  
          print("Connection closed from $nodeId");
          await messageSock.close();
          return;
        default:
          print("Unhandled message: $str");
          break;
      }
      // 使用 messageSock.sock 进行例如 sock.remoteAddress 操作
      // 我推荐在完成后调用 messageSock.close() - 但当调用 zc.close() 时,它也会被关闭
    }
  });
  // 当你想要关闭现有的一切时,可以调用 zc.close()
}

客户端

import 'package:zeroconnect/zeroconnect.dart';

const SERVICE_ID = "YOURSERVICEID";

Future<void> main() async {
  var zc = ZeroConnect(localId: "CLIENT_ID"); // 技术上讲,nodeId 是可选的;它会分配给你一个随机UUID

  var ads = await zc.scan(serviceId: SERVICE_ID, time: const Duration(seconds: 5));
  // OR: var ads = await zc.scan(serviceId: SERVICE_ID, nodeId: NODE_ID);
  // Ad 包含了一个 serviceId 和 nodeId 等信息;详情请查看 Ad 类
  var messageSock = await zc.connect(ads.first); // 也可以使用 (ZeroConnect).connectRaw
  // OR: var messageSock = await zc.connectToFirst(serviceId: SERVICE_ID);
  // OR: var messageSock = await zc.connectToFirst(serviceId: SERVICE_ID, nodeId: NODE_ID, time: const Duration(seconds: 10));
  // 有一天你可能会能够仅指定一个 nodeId,但我尚未解决当时遇到的一些问题。

  await messageSock?.sendString("enable jimjabber");
  await messageSock?.sendString("save msg:");
  await messageSock?.sendString("i love you");
  await messageSock?.sendString("marco");
  print("rx: ${await messageSock?.recvString()}");

  // ...

  await zc.close();
}

你可以选择获取原始套接字而不是MessageSocket,如果你更喜欢这样做。 参见例如 (ZeroConnect).advertiseRaw 和 (ZeroConnect).connectRaw。 实际上,我没有测试过它们,而且由于我最初将Socket包裹在一个MessageSocket中进行一些握手操作,这可能会干扰使用原始Socket。抱歉。 //DUMMY 查看是否可以修复这个问题,并可能添加示例代码

还有一些你可能觉得有用的函数。请检查自动完成并查看源代码。

YOURSERVICEID

据报道(#3),你的服务ID必须是1-15个字符长,并且只能由a-z、A-Z、0-9组成。我个人没有检查过这一点,但我记得遇到过类似的情况(可能是平台相关的),所以如果你遇到问题,请检查你的服务ID是否符合这些限制。

提示

要小心不要让两个节点同时接收消息,否则会导致死锁。 然而,你可以在同一时间发送消息(根据我的测试)。

ZeroConnect 应通过其方法进行操作,但如果你读取字段中的数据,它可能不会立即爆炸。

注意,有些计算机/网络可能会阻止mdns/nsd/zeroconf或外部连接尝试等。

调用broadcast会自动清理死连接。理论上如此。在我用Flutter测试时,发送没有抛出错误。

如果你在发送消息后立即关闭套接字,数据可能无法完全发送。这不是我的错,归咎于套接字。

broadcast 使用 MessageSockets,因此如果你正在使用原始套接字,请注意消息会被前缀一个头部,当前是一个表示后续消息长度的8字节无符号大端长整数,后面跟着相同的但反转并异或(与8字节的0xFF异或)。请参阅MessageSocket

请参阅zeroconnect.dart开头附近以查看日志设置,或者像这样操作:

import 'package:zeroconnect/zeroconnect.dart';

void main() {
  ZC_LOGGING = 10; // 4+ 表示所有内容,目前;-1 表示除了未捕获的异常外什么都没有
  // ...
}

更多关于Flutter零配置连接插件zeroconnect的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter零配置连接插件zeroconnect的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter项目中,使用zeroconnect插件可以帮助你实现零配置连接(ZeroConf),这对于发现和服务网络上的设备非常有用。以下是如何在Flutter项目中使用zeroconnect插件的一个简单示例。

首先,确保你已经在pubspec.yaml文件中添加了zeroconnect依赖:

dependencies:
  flutter:
    sdk: flutter
  zeroconnect: ^最新版本号  # 请替换为最新的版本号

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

接下来,我们来看一个基本的代码示例,展示如何使用zeroconnect插件来浏览和解析网络上的服务。

主文件 main.dart

import 'package:flutter/material.dart';
import 'package:zeroconnect/zeroconnect.dart';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  List<Service> _services = [];

  @override
  void initState() {
    super.initState();
    _startBrowsing();
  }

  void _startBrowsing() async {
    try {
      // 开始浏览服务,这里以 "_http._tcp.local." 为例
      var browser = await Zeroconnect().browse(serviceType: "_http._tcp.local.");
      
      // 监听发现的服务
      browser.serviceAdded.listen((service) {
        setState(() {
          _services.add(service);
        });
      });

      // 监听移除的服务
      browser.serviceRemoved.listen((service) {
        setState(() {
          _services.removeWhere((s) => s.fullName == service.fullName);
        });
      });
    } catch (e) {
      print("Error browsing services: $e");
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: Text('Zeroconnect Example'),
        ),
        body: ListView.builder(
          itemCount: _services.length,
          itemBuilder: (context, index) {
            var service = _services[index];
            return ListTile(
              title: Text(service.fullName),
              subtitle: Text(service.address + ':' + service.port.toString()),
            );
          },
        ),
      ),
    );
  }
}

class Service {
  String fullName;
  String address;
  int port;

  Service({required this.fullName, required this.address, required this.port});
}

注意事项

  1. 权限:在iOS上,使用ZeroConf可能需要网络权限。确保在Info.plist中添加相应的权限声明。
  2. 错误处理:在实际应用中,应该添加更多的错误处理逻辑,以处理网络问题或服务发现失败的情况。
  3. 服务类型:在browse方法中,serviceType参数指定了你想要浏览的服务类型。不同的服务类型可能有不同的前缀和后缀,例如_http._tcp.local._printer._tcp.local.等。

这个示例展示了如何使用zeroconnect插件来浏览网络上的服务,并将发现的服务显示在Flutter应用的列表中。你可以根据需要进一步扩展这个示例,例如解析更多服务信息、连接到发现的服务等。

回到顶部