美文网首页
2023-02-24 flutter attach流程解析1

2023-02-24 flutter attach流程解析1

作者: 我是小胡胡分胡 | 来源:发表于2023-02-23 17:58 被阅读0次

    flutter attach时候经常出现下面这种错误:

    Rerun this command with one of the following passed in as the appId:
    
      flutter attach --app-id com.test.1
      flutter attach --app-id com.test.1 (2)
      flutter attach --app-id com.test.1 (3)
    

    基于此探索一下与flutter attach相关的内容。

    Flutter是一个跨平台的移动应用程序开发框架,Flutter attach是Flutter命令行工具提供的一个命令,用于将开发者的编辑器(如VSCode、Android Studio)连接到正在运行的Flutter应用程序,以便于进行调试。Flutter attach的原理是利用了Dart VM的一个调试协议——VM服务协议,它允许开发者以REST风格的API与Dart VM进行通信。

    Flutter attach的连接流程可以大致分为以下几步:

    • 启动Flutter应用程序:开发者使用Flutter run命令启动Flutter应用程序,该命令将启动Dart VM并加载应用程序代码。

    • 启用VM服务:Dart VM支持一个VM服务,用于向外部应用程序提供调试和诊断功能。Flutter run命令会自动启用VM服务,并监听一个默认的端口号(默认为“8181”)。

    • 连接编辑器:开发者使用Flutter attach命令连接编辑器。Flutter attach命令会尝试连接到运行中的Flutter应用程序的VM服务,连接成功后,将在编辑器中打开一个调试会话。

    • 交互调试:在编辑器中,开发者可以设置断点、单步执行代码、查看变量等,通过与Dart VM服务的交互进行调试。

    需要注意的是,Flutter attach命令要求开发者在启动Flutter应用程序时启用了VM服务。如果在启动应用程序时未启用VM服务,则无法使用Flutter attach命令进行连接。此外,Flutter attach命令还要求运行中的Flutter应用程序与编辑器在同一台计算机上,或者在通过网络进行通信时,必须通过安全的通道进行连接。

    另外flutter attach 命令需要 flutter 应用程序对应的源代码,否则报错:
    Target file "lib/main.dart" not found.
    因为需要热重载和热重启时,需要比对源代码的修改,做出文件同步,这是可以理解的。

    1、attach

    连接到 Flutter 应用程序并启动开发工具和调试服务

    attach-》 _attachToDevice-》getObservatoryUri-》 _client.start(); -》

      @override
      Future<int> attach({
        Completer<DebugConnectionInfo> connectionInfoCompleter,
        Completer<void> appStartedCompleter,
        bool allowExistingDdsInstance = false,
        bool enableDevTools = false,
      }) async {
        _didAttach = true;
        try {
          await connectToServiceProtocol(
            reloadSources: _reloadSourcesService,
            restart: _restartService,
            compileExpression: _compileExpressionService,
            getSkSLMethod: writeSkSL,
            allowExistingDdsInstance: allowExistingDdsInstance,
          );
        // Catches all exceptions, non-Exception objects are rethrown.
        } catch (error) { // ignore: avoid_catches_without_on_clauses
          if (error is! Exception && error is! String) {
            rethrow;
          }
          globals.printError('Error connecting to the service protocol: $error');
          return 2;
        }
    
        if (enableDevTools) {
          // The method below is guaranteed never to return a failing future.
          unawaited(residentDevtoolsHandler.serveAndAnnounceDevTools(
            devToolsServerAddress: debuggingOptions.devToolsServerAddress,
            flutterDevices: flutterDevices,
          ));
        }
    
        for (final FlutterDevice device in flutterDevices) {
          await device.initLogReader();
        }
        try {
          final List<Uri> baseUris = await _initDevFS();
          if (connectionInfoCompleter != null) {
            // Only handle one debugger connection.
            connectionInfoCompleter.complete(
              DebugConnectionInfo(
                httpUri: flutterDevices.first.vmService.httpAddress,
                wsUri: flutterDevices.first.vmService.wsAddress,
                baseUri: baseUris.first.toString(),
              ),
            );
          }
        } on DevFSException catch (error) {
          globals.printError('Error initializing DevFS: $error');
          return 3;
        }
    
        final Stopwatch initialUpdateDevFSsTimer = Stopwatch()..start();
        final UpdateFSReport devfsResult = await _updateDevFS(fullRestart: true);
        _addBenchmarkData(
          'hotReloadInitialDevFSSyncMilliseconds',
          initialUpdateDevFSsTimer.elapsed.inMilliseconds,
        );
        if (!devfsResult.success) {
          return 3;
        }
    
        for (final FlutterDevice device in flutterDevices) {
          // VM must have accepted the kernel binary, there will be no reload
          // report, so we let incremental compiler know that source code was accepted.
          if (device.generator != null) {
            device.generator.accept();
          }
          final List<FlutterView> views = await device.vmService.getFlutterViews();
          for (final FlutterView view in views) {
            globals.printTrace('Connected to $view.');
          }
        }
    
        // In fast-start mode, apps are initialized from a placeholder splashscreen
        // app. We must do a restart here to load the program and assets for the
        // real app.
        if (debuggingOptions.fastStart) {
          await restart(
            fullRestart: true,
            reason: 'restart',
            silent: true,
          );
        }
    
        appStartedCompleter?.complete();
    
        if (benchmarkMode) {
          // Wait multiple seconds for the isolate to have fully started.
          await Future<void>.delayed(const Duration(seconds: 10));
          // We are running in benchmark mode.
          globals.printStatus('Running in benchmark mode.');
          // Measure time to perform a hot restart.
          globals.printStatus('Benchmarking hot restart');
          await restart(fullRestart: true);
          // Wait multiple seconds to stabilize benchmark on slower device lab hardware.
          // Hot restart finishes when the new isolate is started, not when the new isolate
          // is ready. This process can actually take multiple seconds.
          await Future<void>.delayed(const Duration(seconds: 10));
    
          globals.printStatus('Benchmarking hot reload');
          // Measure time to perform a hot reload.
          await restart();
          if (stayResident) {
            await waitForAppToFinish();
          } else {
            globals.printStatus('Benchmark completed. Exiting application.');
            await _cleanupDevFS();
            await stopEchoingDeviceLog();
            await exitApp();
          }
          final File benchmarkOutput = globals.fs.file('hot_benchmark.json');
          benchmarkOutput.writeAsStringSync(toPrettyJson(benchmarkData));
          return 0;
        }
        writeVmServiceFile();
    
        int result = 0;
        if (stayResident) {
          result = await waitForAppToFinish();
        }
        await cleanupAtFinish();
        return result;
      }
    

    上面的代码是 Flutter 开发框架中的一个函数,它在调试模式下连接到 Flutter 应用程序并启动开发工具和调试服务。它有几个参数,用于控制连接和初始化的行为。

    • 函数首先将 _didAttach 标记设置为 true,以表示已经连接到调试服务。然后,它通过调用 connectToServiceProtocol 函数来连接到服务协议,并通过传递几个服务对象来注册服务。

    • 如果连接过程中出现错误,则函数会打印错误消息并返回 2。

    • 如果 enableDevTools 参数设置为 true,则函数会启动开发工具,并在开发工具服务器地址上向客户端广播 DevTools 的可用性。

    • 接下来,函数将对每个 Flutter 设备调用 initLogReader 方法以初始化日志读取器。然后,它将调用 _initDevFS 方法来初始化开发文件系统(DevFS)并获取基本 URI。如果 connectionInfoCompleter 参数不为空,则函数将使用第一个 Flutter 设备的 VM 服务地址和基本 URI 完成 DebugConnectionInfo 对象。

    • 如果在初始化 DevFS 过程中出现错误,则函数会打印错误消息并返回 3。

    • 接下来,函数将调用 _updateDevFS 方法来更新开发文件系统,并将 fullRestart 参数设置为 true。如果更新失败,则函数将返回 3。

    • 然后,函数将对每个 Flutter 设备调用 getFlutterViews 方法以获取 Flutter 视图,并打印连接成功的消息。

    • 如果 debuggingOptions.fastStart 参数设置为 true,则函数将调用 restart 方法以进行全面重启,并在静默模式下重新启动应用程序。

    • 如果 benchmarkMode 参数设置为 true,则函数将测量性能并记录测试结果。首先,函数将等待 10 秒钟以确保隔离环境完全启动。然后,函数将打印开始基准测试的消息,并调用 restart 方法以进行全面重启。然后,函数将再次等待 10 秒钟,以稳定基准测试结果。接下来,函数将打印开始基准测试热重载的消息,并调用 restart 方法以进行热重载。如果 stayResident 参数设置为 true,则函数将等待应用程序运行完成,否则函数将清理 DevFS、停止日志记录并退出应用程序。最后,函数将使用 toPrettyJson 函数将基准测试结果写入文件,并返回 0。

    • 最后,函数将调用 writeVmServiceFile 方法以将 VM 服务地址写入文件。如果 stayResident 参数设置为 true,则函数将调用 waitForAppToFinish 方法并返回其结果。否则,函数将调用 cleanupAtFinish 方法以清理资源,并返回 0。

    2、_attachToDevice

    Future<void> _attachToDevice(Device device) async {
        final FlutterProject flutterProject = FlutterProject.current();
    
        final Daemon daemon = boolArg('machine')
          ? Daemon(
              DaemonConnection(
                daemonStreams: DaemonStreams.fromStdio(globals.stdio, logger: globals.logger),
                logger: globals.logger,
              ),
              notifyingLogger: (globals.logger is NotifyingLogger)
                ? globals.logger as NotifyingLogger
                : NotifyingLogger(verbose: globals.logger.isVerbose, parent: globals.logger),
              logToStdout: true,
            )
          : null;
    
        Stream<Uri> observatoryUri;
        bool usesIpv6 = ipv6;
        final String ipv6Loopback = InternetAddress.loopbackIPv6.address;
        final String ipv4Loopback = InternetAddress.loopbackIPv4.address;
        final String hostname = usesIpv6 ? ipv6Loopback : ipv4Loopback;
    
        if (debugPort == null && debugUri == null) {
          if (device is FuchsiaDevice) {
            final String module = stringArg('module');
            if (module == null) {
              throwToolExit("'--module' is required for attaching to a Fuchsia device");
            }
            usesIpv6 = device.ipv6;
            FuchsiaIsolateDiscoveryProtocol isolateDiscoveryProtocol;
            try {
              isolateDiscoveryProtocol = device.getIsolateDiscoveryProtocol(module);
              observatoryUri = Stream<Uri>.value(await isolateDiscoveryProtocol.uri).asBroadcastStream();
            } on Exception {
              isolateDiscoveryProtocol?.dispose();
              final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
              for (final ForwardedPort port in ports) {
                await device.portForwarder.unforward(port);
              }
              rethrow;
            }
          } else if ((device is IOSDevice) || (device is IOSSimulator) || (device is MacOSDesignedForIPadDevice)) {
            final Uri uriFromMdns =
              await MDnsObservatoryDiscovery.instance.getObservatoryUri(
                appId,
                device,
                usesIpv6: usesIpv6,
                deviceVmservicePort: deviceVmservicePort,
              );
            observatoryUri = uriFromMdns == null
              ? null
              : Stream<Uri>.value(uriFromMdns).asBroadcastStream();
          }
          // If MDNS discovery fails or we're not on iOS, fallback to ProtocolDiscovery.
          if (observatoryUri == null) {
            final ProtocolDiscovery observatoryDiscovery =
              ProtocolDiscovery.observatory(
                // If it's an Android device, attaching relies on past log searching
                // to find the service protocol.
                await device.getLogReader(includePastLogs: device is AndroidDevice),
                portForwarder: device.portForwarder,
                ipv6: ipv6,
                devicePort: deviceVmservicePort,
                hostPort: hostVmservicePort,
                logger: globals.logger,
              );
            globals.printStatus('Waiting for a connection from Flutter on ${device.name}...');
            observatoryUri = observatoryDiscovery.uris;
            // Determine ipv6 status from the scanned logs.
            usesIpv6 = observatoryDiscovery.ipv6;
          }
        } else {
          observatoryUri = Stream<Uri>
            .fromFuture(
              buildObservatoryUri(
                device,
                debugUri?.host ?? hostname,
                debugPort ?? debugUri.port,
                hostVmservicePort,
                debugUri?.path,
              )
            ).asBroadcastStream();
        }
    
        globals.terminal.usesTerminalUi = daemon == null;
    
        try {
          int result;
          if (daemon != null) {
            final ResidentRunner runner = await createResidentRunner(
              observatoryUris: observatoryUri,
              device: device,
              flutterProject: flutterProject,
              usesIpv6: usesIpv6,
            );
            AppInstance app;
            try {
              app = await daemon.appDomain.launch(
                runner,
                ({Completer<DebugConnectionInfo> connectionInfoCompleter,
                  Completer<void> appStartedCompleter}) {
                  return runner.attach(
                    connectionInfoCompleter: connectionInfoCompleter,
                    appStartedCompleter: appStartedCompleter,
                    allowExistingDdsInstance: true,
                    enableDevTools: boolArg(FlutterCommand.kEnableDevTools),
                  );
                },
                device,
                null,
                true,
                globals.fs.currentDirectory,
                LaunchMode.attach,
                globals.logger as AppRunLogger,
              );
            } on Exception catch (error) {
              throwToolExit(error.toString());
            }
            result = await app.runner.waitForAppToFinish();
            assert(result != null);
            return;
          }
          while (true) {
            final ResidentRunner runner = await createResidentRunner(
              observatoryUris: observatoryUri,
              device: device,
              flutterProject: flutterProject,
              usesIpv6: usesIpv6,
            );
            final Completer<void> onAppStart = Completer<void>.sync();
            TerminalHandler terminalHandler;
            unawaited(onAppStart.future.whenComplete(() {
              terminalHandler = TerminalHandler(
                runner,
                logger: globals.logger,
                terminal: globals.terminal,
                signals: globals.signals,
                processInfo: globals.processInfo,
                reportReady: boolArg('report-ready'),
                pidFile: stringArg('pid-file'),
              )
                ..registerSignalHandlers()
                ..setupTerminal();
            }));
            result = await runner.attach(
              appStartedCompleter: onAppStart,
              allowExistingDdsInstance: true,
              enableDevTools: boolArg(FlutterCommand.kEnableDevTools),
            );
            if (result != 0) {
              throwToolExit(null, exitCode: result);
            }
            terminalHandler?.stop();
            assert(result != null);
            if (runner.exited || !runner.isWaitingForObservatory) {
              break;
            }
            globals.printStatus('Waiting for a new connection from Flutter on ${device.name}...');
          }
        } on RPCError catch (err) {
          if (err.code == RPCErrorCodes.kServiceDisappeared) {
            throwToolExit('Lost connection to device.');
          }
          rethrow;
        } finally {
          final List<ForwardedPort> ports = device.portForwarder.forwardedPorts.toList();
          for (final ForwardedPort port in ports) {
            await device.portForwarder.unforward(port);
          }
        }
      }
    
    • 这是一段 Flutter 命令行工具的 Dart 代码,具体功能是将一个 Flutter 应用程序附加到特定设备的调试器上,以便进行调试。
    • 在这段代码中,根据设备类型选择不同的附加方式。例如,如果是 Fuchsia 设备,则使用 FuchsiaIsolateDiscoveryProtocol 协议来查找应用程序,如果是 iOS 设备,则使用 MDnsObservatoryDiscovery 协议查找。如果以上两种方法都失败,则使用 ProtocolDiscovery 协议查找。
    • 在找到应用程序的 Uri 后,该应用程序会使用运行中的 daemon 或创建新的 daemon 与设备进行通信。

    找到uri http://127.0.0.1:55177/RXKA2jepV60=/
    运行while循环接收指令:

    while (true) {
            final ResidentRunner runner = await createResidentRunner(
              observatoryUris: observatoryUri,
              device: device,
              flutterProject: flutterProject,
              usesIpv6: usesIpv6,
            );
            final Completer<void> onAppStart = Completer<void>.sync();
            TerminalHandler terminalHandler;
            unawaited(onAppStart.future.whenComplete(() {
              terminalHandler = TerminalHandler(
                runner,
                logger: globals.logger,
                terminal: globals.terminal,
                signals: globals.signals,
                processInfo: globals.processInfo,
                reportReady: boolArg('report-ready'),
                pidFile: stringArg('pid-file'),
              )
                ..registerSignalHandlers()
                ..setupTerminal();
            }));
            result = await runner.attach(
              appStartedCompleter: onAppStart,
              allowExistingDdsInstance: true,
              enableDevTools: boolArg(FlutterCommand.kEnableDevTools),
            );
            if (result != 0) {
              throwToolExit(null, exitCode: result);
            }
            terminalHandler?.stop();
            assert(result != null);
            if (runner.exited || !runner.isWaitingForObservatory) {
              break;
            }
            globals.printStatus('Waiting for a new connection from Flutter on ${device.name}...');
          }
    

    3、getObservatoryUri

     @visibleForTesting
      Future<MDnsObservatoryDiscoveryResult?> query({String? applicationId, int? deviceVmservicePort}) async {
        _logger.printTrace('Checking for advertised Dart observatories...');
        try {
          await _client.start();
          final List<PtrResourceRecord> pointerRecords = await _client
            .lookup<PtrResourceRecord>(
              ResourceRecordQuery.serverPointer(dartObservatoryName),
            )
            .toList();
          if (pointerRecords.isEmpty) {
            _logger.printTrace('No pointer records found.');
            return null;
          }
          // We have no guarantee that we won't get multiple hits from the same
          // service on this.
          final Set<String> uniqueDomainNames = pointerRecords
            .map<String>((PtrResourceRecord record) => record.domainName)
            .toSet();
    
          String? domainName;
          if (applicationId != null) {
            for (final String name in uniqueDomainNames) {
              if (name.toLowerCase().startsWith(applicationId.toLowerCase())) {
                domainName = name;
                break;
              }
            }
            if (domainName == null) {
              throwToolExit('Did not find a observatory port advertised for $applicationId.');
            }
          } else if (uniqueDomainNames.length > 1) {
            final StringBuffer buffer = StringBuffer();
            buffer.writeln('There are multiple observatory ports available.');
            buffer.writeln('Rerun this command with one of the following passed in as the appId:');
            buffer.writeln();
            for (final String uniqueDomainName in uniqueDomainNames) {
              buffer.writeln('  flutter attach --app-id ${uniqueDomainName.replaceAll('.$dartObservatoryName', '')}');
            }
            throwToolExit(buffer.toString());
          } else {
            domainName = pointerRecords[0].domainName;
          }
          _logger.printTrace('Checking for available port on $domainName');
          // Here, if we get more than one, it should just be a duplicate.
          final List<SrvResourceRecord> srv = await _client
            .lookup<SrvResourceRecord>(
              ResourceRecordQuery.service(domainName),
            )
            .toList();
          if (srv.isEmpty) {
            return null;
          }
          if (srv.length > 1) {
            _logger.printWarning('Unexpectedly found more than one observatory report for $domainName '
                       '- using first one (${srv.first.port}).');
          }
          _logger.printTrace('Checking for authentication code for $domainName');
          final List<TxtResourceRecord> txt = await _client
            .lookup<TxtResourceRecord>(
                ResourceRecordQuery.text(domainName),
            )
            .toList();
          if (txt == null || txt.isEmpty) {
            return MDnsObservatoryDiscoveryResult(srv.first.port, '');
          }
          const String authCodePrefix = 'authCode=';
          String? raw;
          for (final String record in txt.first.text.split('\n')) {
            if (record.startsWith(authCodePrefix)) {
              raw = record;
              break;
            }
          }
          if (raw == null) {
            return MDnsObservatoryDiscoveryResult(srv.first.port, '');
          }
          String authCode = raw.substring(authCodePrefix.length);
          // The Observatory currently expects a trailing '/' as part of the
          // URI, otherwise an invalid authentication code response is given.
          if (!authCode.endsWith('/')) {
            authCode += '/';
          }
          return MDnsObservatoryDiscoveryResult(srv.first.port, authCode);
        } finally {
          _client.stop();
        }
      }
    

    代码流程如下:

    • 打印日志,开始查找已经广告的Dart Observatory。
    • 启动MDNS客户端。
    • 通过客户端查询指向Dart Observatory的指针记录(PtrResourceRecord)。
    • 如果找不到指针记录,打印日志并返回null。
    • 如果找到指针记录,将其唯一的域名添加到集合中。
    • 如果提供了应用程序ID,则在集合中查找以该ID开头的唯一域名。如果找不到,则抛出异常。
    • 如果未提供应用程序ID,并且集合中有多个唯一的域名,则打印建议的应用程序ID并抛出异常。
    • 如果未提供应用程序ID,并且集合中只有一个唯一的域名,则使用该唯一的域名。
    • 检查所选域名上是否有可用端口。
    • 如果有多个服务记录(SrvResourceRecord),则使用第一个记录的端口。
    • 检查所选域名上是否有身份验证代码(authCode)。
    • 如果没有身份验证代码,则返回使用第一个服务记录的端口和空的身份验证代码的MDnsObservatoryDiscoveryResult。
    • 如果有身份验证代码,则从TXT资源记录中提取该代码。
    • 如果找不到身份验证代码,则返回使用第一个服务记录的端口和空的身份验证代码的MDnsObservatoryDiscoveryResult。
    • 如果找到了身份验证代码,则将其分配给MDnsObservatoryDiscoveryResult,同时确保代码以"/"结尾。
    • 停止MDNS客户端。
    • 返回使用所选域名的第一个服务记录的端口和身份验证代码的MDnsObservatoryDiscoveryResult。

    总的来说,每一次的attach都会启动一个启动MDNS客户端,如果启动失败,则停止MDNS客户端。

    4、 await _client.start();

    Future<void> start({
        InternetAddress? listenAddress,
        NetworkInterfacesFactory? interfacesFactory,
        int mDnsPort = mDnsPort,
        InternetAddress? mDnsAddress,
      }) async {
        listenAddress ??= InternetAddress.anyIPv4;
        interfacesFactory ??= allInterfacesFactory;
    
        assert(listenAddress.address == InternetAddress.anyIPv4.address ||
            listenAddress.address == InternetAddress.anyIPv6.address);
    
        if (_started || _starting) {
          return;
        }
        _starting = true;
    
        final int selectedMDnsPort = _mDnsPort = mDnsPort;
        _mDnsAddress = mDnsAddress;
    
        // Listen on all addresses.
        final RawDatagramSocket incoming = await _rawDatagramSocketFactory(
          listenAddress.address,
          selectedMDnsPort,
          reuseAddress: true,
          reusePort: true,
          ttl: 255,
        );
    
        // Can't send to IPv6 any address.
        if (incoming.address != InternetAddress.anyIPv6) {
          _sockets.add(incoming);
        } else {
          _toBeClosed.add(incoming);
        }
    
        _mDnsAddress ??= incoming.address.type == InternetAddressType.IPv4
            ? mDnsAddressIPv4
            : mDnsAddressIPv6;
    
        final List<NetworkInterface> interfaces =
            (await interfacesFactory(listenAddress.type)).toList();
    
        for (final NetworkInterface interface in interfaces) {
          // Create a socket for sending on each adapter.
          final InternetAddress targetAddress = interface.addresses[0];
          final RawDatagramSocket socket = await _rawDatagramSocketFactory(
            targetAddress,
            selectedMDnsPort,
            reuseAddress: true,
            reusePort: true,
            ttl: 255,
          );
          _sockets.add(socket);
          // Ensure that we're using this address/interface for multicast.
          if (targetAddress.type == InternetAddressType.IPv4) {
            socket.setRawOption(RawSocketOption(
              RawSocketOption.levelIPv4,
              RawSocketOption.IPv4MulticastInterface,
              targetAddress.rawAddress,
            ));
          } else {
            socket.setRawOption(RawSocketOption.fromInt(
              RawSocketOption.levelIPv6,
              RawSocketOption.IPv6MulticastInterface,
              interface.index,
            ));
          }
          // Join multicast on this interface.
          incoming.joinMulticast(_mDnsAddress!, interface);
        }
        incoming.listen((RawSocketEvent event) => _handleIncoming(event, incoming));
        _started = true;
        _starting = false;
      }
    
    • 检查是否已经启动或正在启动,如果是则直接返回。

    • 初始化网络地址、接口工厂等参数。

    • 创建一个 RawDatagramSocket 对象,用于接收网络数据。通过 _rawDatagramSocketFactory 方法创建并设置监听地址、端口、地址重用、端口重用等选项。

    • 将创建的 RawDatagramSocket 对象添加到 _sockets 列表中,如果地址为 InternetAddress.anyIPv6,则添加到 _toBeClosed 列表中。

    • 确定 mDNS 地址,如果没有传入 mDNS 地址,则根据监听地址类型选择 IPv4 或 IPv6 的默认 mDNS 地址。

    • 获取本地网络接口列表,并对每个接口创建一个 RawDatagramSocket 对象,用于发送网络数据。对每个接口设置监听地址、端口、地址重用、端口重用等选项,并添加到 _sockets 列表中。对于 IPv4 接口,使用 setRawOption 方法设置 IPv4 组播接口,对于 IPv6 接口,使用 setRawOption 方法设置 IPv6 组播接口。

    _sockets.add(socket);会发现有3个sockets
    0.0.0.0,127.0.0.1,253.53.111.111 这三个ip地址应该对应是同一个主机。

    • 对接收 RawDatagramSocket 对象调用 joinMulticast 方法,加入 mDNS 组播地址和本地网络接口。
    • 对接收 RawDatagramSocket 对象调用 listen 方法,监听网络事件并调用 _handleIncoming 方法处理网络数据。
      // Process incoming datagrams.
      void _handleIncoming(RawSocketEvent event, RawDatagramSocket incoming) {
        if (event == RawSocketEvent.read) {
          final Datagram? datagram = incoming.receive();
          if (datagram == null) {
            return;
          }
    
          // Check for published responses.
          final List<ResourceRecord>? response = decodeMDnsResponse(datagram.data);
          if (response != null) {
            _cache.updateRecords(response);
            _resolver.handleResponse(response);
            return;
          }
          // TODO(dnfield): Support queries coming in for published entries.
        }
      }
    

    在_handleIncoming的数据回调中,可看到数据长这样:

    image.png
    • 设置 _started 标志表示已启动,设置 _starting 标志表示正在启动。

    总的来说,在Flutter中,mdnsclient.start是启动一个mDNS客户端的方法,用于在本地网络上发现可用的服务。

    mDNS是一种广泛使用的服务发现协议,可以通过在本地网络中进行广播和响应来发现可用的服务。mDNS客户端使用查询报文向本地网络中的所有设备发送请求,以查找可用的服务。一旦某个设备响应了请求,mDNS客户端就会接收到包含服务信息的响应报文。

    mdnsclient.start方法会启动一个mDNS客户端,并开始向本地网络中发送查询报文。当发现可用的服务时,客户端将回调一个提供服务信息的回调函数,以便应用程序可以处理这些信息。通过这种方式,应用程序可以在本地网络中发现可用的服务,并使用这些服务进行网络通信。

    5、 MDnsObservatoryDiscoveryResult(srv.first.port, authCode)

    相关文章

      网友评论

          本文标题:2023-02-24 flutter attach流程解析1

          本文链接:https://www.haomeiwen.com/subject/nthtldtx.html