flutter attach時(shí)候經(jīng)常出現(xiàn)下面這種錯(cuò)誤:
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相關(guān)的內(nèi)容侣灶。
Flutter是一個(gè)跨平臺(tái)的移動(dòng)應(yīng)用程序開發(fā)框架嵌言,F(xiàn)lutter attach是Flutter命令行工具提供的一個(gè)命令,用于將開發(fā)者的編輯器(如VSCode键兜、Android Studio)連接到正在運(yùn)行的Flutter應(yīng)用程序阻课,以便于進(jìn)行調(diào)試缕允。Flutter attach的原理是利用了Dart VM的一個(gè)調(diào)試協(xié)議——VM服務(wù)協(xié)議,它允許開發(fā)者以REST風(fēng)格的API與Dart VM進(jìn)行通信掂恕。
Flutter attach的連接流程可以大致分為以下幾步:
啟動(dòng)Flutter應(yīng)用程序:開發(fā)者使用Flutter run命令啟動(dòng)Flutter應(yīng)用程序拖陆,該命令將啟動(dòng)Dart VM并加載應(yīng)用程序代碼。
啟用VM服務(wù):Dart VM支持一個(gè)VM服務(wù)懊亡,用于向外部應(yīng)用程序提供調(diào)試和診斷功能依啰。Flutter run命令會(huì)自動(dòng)啟用VM服務(wù),并監(jiān)聽一個(gè)默認(rèn)的端口號(hào)(默認(rèn)為“8181”)店枣。
連接編輯器:開發(fā)者使用Flutter attach命令連接編輯器速警。Flutter attach命令會(huì)嘗試連接到運(yùn)行中的Flutter應(yīng)用程序的VM服務(wù)叹誉,連接成功后,將在編輯器中打開一個(gè)調(diào)試會(huì)話闷旧。
交互調(diào)試:在編輯器中长豁,開發(fā)者可以設(shè)置斷點(diǎn)、單步執(zhí)行代碼忙灼、查看變量等匠襟,通過(guò)與Dart VM服務(wù)的交互進(jìn)行調(diào)試。
需要注意的是该园,F(xiàn)lutter attach命令要求開發(fā)者在啟動(dòng)Flutter應(yīng)用程序時(shí)啟用了VM服務(wù)酸舍。如果在啟動(dòng)應(yīng)用程序時(shí)未啟用VM服務(wù),則無(wú)法使用Flutter attach命令進(jìn)行連接里初。此外啃勉,F(xiàn)lutter attach命令還要求運(yùn)行中的Flutter應(yīng)用程序與編輯器在同一臺(tái)計(jì)算機(jī)上,或者在通過(guò)網(wǎng)絡(luò)進(jìn)行通信時(shí)双妨,必須通過(guò)安全的通道進(jìn)行連接淮阐。
另外flutter attach 命令需要 flutter 應(yīng)用程序?qū)?yīng)的源代碼,否則報(bào)錯(cuò):
Target file "lib/main.dart" not found.
因?yàn)樾枰獰嶂剌d和熱重啟時(shí)刁品,需要比對(duì)源代碼的修改枝嘶,做出文件同步,這是可以理解的哑诊。
1、attach
連接到 Flutter 應(yīng)用程序并啟動(dòng)開發(fā)工具和調(diào)試服務(wù)
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 開發(fā)框架中的一個(gè)函數(shù)及刻,它在調(diào)試模式下連接到 Flutter 應(yīng)用程序并啟動(dòng)開發(fā)工具和調(diào)試服務(wù)镀裤。它有幾個(gè)參數(shù),用于控制連接和初始化的行為缴饭。
函數(shù)首先將 _didAttach 標(biāo)記設(shè)置為 true暑劝,以表示已經(jīng)連接到調(diào)試服務(wù)。然后颗搂,它通過(guò)調(diào)用 connectToServiceProtocol 函數(shù)來(lái)連接到服務(wù)協(xié)議担猛,并通過(guò)傳遞幾個(gè)服務(wù)對(duì)象來(lái)注冊(cè)服務(wù)。
如果連接過(guò)程中出現(xiàn)錯(cuò)誤丢氢,則函數(shù)會(huì)打印錯(cuò)誤消息并返回 2傅联。
如果 enableDevTools 參數(shù)設(shè)置為 true,則函數(shù)會(huì)啟動(dòng)開發(fā)工具疚察,并在開發(fā)工具服務(wù)器地址上向客戶端廣播 DevTools 的可用性蒸走。
接下來(lái),函數(shù)將對(duì)每個(gè) Flutter 設(shè)備調(diào)用 initLogReader 方法以初始化日志讀取器貌嫡。然后比驻,它將調(diào)用 _initDevFS 方法來(lái)初始化開發(fā)文件系統(tǒng)(DevFS)并獲取基本 URI该溯。如果 connectionInfoCompleter 參數(shù)不為空,則函數(shù)將使用第一個(gè) Flutter 設(shè)備的 VM 服務(wù)地址和基本 URI 完成 DebugConnectionInfo 對(duì)象别惦。
如果在初始化 DevFS 過(guò)程中出現(xiàn)錯(cuò)誤狈茉,則函數(shù)會(huì)打印錯(cuò)誤消息并返回 3。
接下來(lái)掸掸,函數(shù)將調(diào)用 _updateDevFS 方法來(lái)更新開發(fā)文件系統(tǒng)氯庆,并將 fullRestart 參數(shù)設(shè)置為 true。如果更新失敗猾漫,則函數(shù)將返回 3点晴。
然后,函數(shù)將對(duì)每個(gè) Flutter 設(shè)備調(diào)用 getFlutterViews 方法以獲取 Flutter 視圖悯周,并打印連接成功的消息粒督。
如果 debuggingOptions.fastStart 參數(shù)設(shè)置為 true,則函數(shù)將調(diào)用 restart 方法以進(jìn)行全面重啟禽翼,并在靜默模式下重新啟動(dòng)應(yīng)用程序屠橄。
如果 benchmarkMode 參數(shù)設(shè)置為 true,則函數(shù)將測(cè)量性能并記錄測(cè)試結(jié)果闰挡。首先锐墙,函數(shù)將等待 10 秒鐘以確保隔離環(huán)境完全啟動(dòng)。然后长酗,函數(shù)將打印開始基準(zhǔn)測(cè)試的消息溪北,并調(diào)用 restart 方法以進(jìn)行全面重啟。然后夺脾,函數(shù)將再次等待 10 秒鐘之拨,以穩(wěn)定基準(zhǔn)測(cè)試結(jié)果。接下來(lái)咧叭,函數(shù)將打印開始基準(zhǔn)測(cè)試熱重載的消息蚀乔,并調(diào)用 restart 方法以進(jìn)行熱重載。如果 stayResident 參數(shù)設(shè)置為 true菲茬,則函數(shù)將等待應(yīng)用程序運(yùn)行完成吉挣,否則函數(shù)將清理 DevFS、停止日志記錄并退出應(yīng)用程序婉弹。最后睬魂,函數(shù)將使用 toPrettyJson 函數(shù)將基準(zhǔn)測(cè)試結(jié)果寫入文件,并返回 0镀赌。
最后汉买,函數(shù)將調(diào)用 writeVmServiceFile 方法以將 VM 服務(wù)地址寫入文件。如果 stayResident 參數(shù)設(shè)置為 true佩脊,則函數(shù)將調(diào)用 waitForAppToFinish 方法并返回其結(jié)果蛙粘。否則垫卤,函數(shù)將調(diào)用 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 代碼,具體功能是將一個(gè) Flutter 應(yīng)用程序附加到特定設(shè)備的調(diào)試器上舔痕,以便進(jìn)行調(diào)試评抚。
- 在這段代碼中,根據(jù)設(shè)備類型選擇不同的附加方式伯复。例如慨代,如果是 Fuchsia 設(shè)備,則使用 FuchsiaIsolateDiscoveryProtocol 協(xié)議來(lái)查找應(yīng)用程序啸如,如果是 iOS 設(shè)備侍匙,則使用 MDnsObservatoryDiscovery 協(xié)議查找。如果以上兩種方法都失敗叮雳,則使用 ProtocolDiscovery 協(xié)議查找想暗。
- 在找到應(yīng)用程序的 Uri 后,該應(yīng)用程序會(huì)使用運(yùn)行中的 daemon 或創(chuàng)建新的 daemon 與設(shè)備進(jìn)行通信帘不。
找到uri http://127.0.0.1:55177/RXKA2jepV60=/
運(yùn)行while循環(huán)接收指令:
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();
}
}
代碼流程如下:
- 打印日志,開始查找已經(jīng)廣告的Dart Observatory寞焙。
- 啟動(dòng)MDNS客戶端储狭。
- 通過(guò)客戶端查詢指向Dart Observatory的指針記錄(PtrResourceRecord)。
- 如果找不到指針記錄捣郊,打印日志并返回null辽狈。
- 如果找到指針記錄,將其唯一的域名添加到集合中模她。
- 如果提供了應(yīng)用程序ID,則在集合中查找以該ID開頭的唯一域名懂牧。如果找不到侈净,則拋出異常。
- 如果未提供應(yīng)用程序ID僧凤,并且集合中有多個(gè)唯一的域名畜侦,則打印建議的應(yīng)用程序ID并拋出異常。
- 如果未提供應(yīng)用程序ID躯保,并且集合中只有一個(gè)唯一的域名旋膳,則使用該唯一的域名。
- 檢查所選域名上是否有可用端口途事。
- 如果有多個(gè)服務(wù)記錄(SrvResourceRecord)验懊,則使用第一個(gè)記錄的端口擅羞。
- 檢查所選域名上是否有身份驗(yàn)證代碼(authCode)。
- 如果沒(méi)有身份驗(yàn)證代碼义图,則返回使用第一個(gè)服務(wù)記錄的端口和空的身份驗(yàn)證代碼的MDnsObservatoryDiscoveryResult减俏。
- 如果有身份驗(yàn)證代碼,則從TXT資源記錄中提取該代碼碱工。
- 如果找不到身份驗(yàn)證代碼娃承,則返回使用第一個(gè)服務(wù)記錄的端口和空的身份驗(yàn)證代碼的MDnsObservatoryDiscoveryResult。
- 如果找到了身份驗(yàn)證代碼怕篷,則將其分配給MDnsObservatoryDiscoveryResult历筝,同時(shí)確保代碼以"/"結(jié)尾。
- 停止MDNS客戶端廊谓。
- 返回使用所選域名的第一個(gè)服務(wù)記錄的端口和身份驗(yàn)證代碼的MDnsObservatoryDiscoveryResult梳猪。
總的來(lái)說(shuō),每一次的attach都會(huì)啟動(dòng)一個(gè)啟動(dòng)MDNS客戶端蹂析,如果啟動(dòng)失敗舔示,則停止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;
}
檢查是否已經(jīng)啟動(dòng)或正在啟動(dòng)惕稻,如果是則直接返回。
初始化網(wǎng)絡(luò)地址蝙叛、接口工廠等參數(shù)俺祠。
創(chuàng)建一個(gè) RawDatagramSocket 對(duì)象,用于接收網(wǎng)絡(luò)數(shù)據(jù)借帘。通過(guò) _rawDatagramSocketFactory 方法創(chuàng)建并設(shè)置監(jiān)聽地址蜘渣、端口、地址重用肺然、端口重用等選項(xiàng)蔫缸。
將創(chuàng)建的 RawDatagramSocket 對(duì)象添加到 _sockets 列表中,如果地址為 InternetAddress.anyIPv6际起,則添加到 _toBeClosed 列表中拾碌。
確定 mDNS 地址,如果沒(méi)有傳入 mDNS 地址街望,則根據(jù)監(jiān)聽地址類型選擇 IPv4 或 IPv6 的默認(rèn) mDNS 地址校翔。
獲取本地網(wǎng)絡(luò)接口列表,并對(duì)每個(gè)接口創(chuàng)建一個(gè) RawDatagramSocket 對(duì)象灾前,用于發(fā)送網(wǎng)絡(luò)數(shù)據(jù)防症。對(duì)每個(gè)接口設(shè)置監(jiān)聽地址、端口、地址重用蔫敲、端口重用等選項(xiàng)饲嗽,并添加到 _sockets 列表中。對(duì)于 IPv4 接口燕偶,使用 setRawOption 方法設(shè)置 IPv4 組播接口喝噪,對(duì)于 IPv6 接口,使用 setRawOption 方法設(shè)置 IPv6 組播接口指么。
_sockets.add(socket);會(huì)發(fā)現(xiàn)有3個(gè)sockets
0.0.0.0酝惧,127.0.0.1,253.53.111.111 這三個(gè)ip地址應(yīng)該對(duì)應(yīng)是同一個(gè)主機(jī)伯诬。
- 對(duì)接收 RawDatagramSocket 對(duì)象調(diào)用 joinMulticast 方法晚唇,加入 mDNS 組播地址和本地網(wǎng)絡(luò)接口。
- 對(duì)接收 RawDatagramSocket 對(duì)象調(diào)用 listen 方法盗似,監(jiān)聽網(wǎng)絡(luò)事件并調(diào)用 _handleIncoming 方法處理網(wǎng)絡(luò)數(shù)據(jù)哩陕。
// 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的數(shù)據(jù)回調(diào)中,可看到數(shù)據(jù)長(zhǎng)這樣:
- 設(shè)置 _started 標(biāo)志表示已啟動(dòng)赫舒,設(shè)置 _starting 標(biāo)志表示正在啟動(dòng)悍及。
總的來(lái)說(shuō),在Flutter中接癌,mdnsclient.start是啟動(dòng)一個(gè)mDNS客戶端的方法心赶,用于在本地網(wǎng)絡(luò)上發(fā)現(xiàn)可用的服務(wù)。
mDNS是一種廣泛使用的服務(wù)發(fā)現(xiàn)協(xié)議缺猛,可以通過(guò)在本地網(wǎng)絡(luò)中進(jìn)行廣播和響應(yīng)來(lái)發(fā)現(xiàn)可用的服務(wù)缨叫。mDNS客戶端使用查詢報(bào)文向本地網(wǎng)絡(luò)中的所有設(shè)備發(fā)送請(qǐng)求,以查找可用的服務(wù)荔燎。一旦某個(gè)設(shè)備響應(yīng)了請(qǐng)求耻姥,mDNS客戶端就會(huì)接收到包含服務(wù)信息的響應(yīng)報(bào)文。
mdnsclient.start方法會(huì)啟動(dòng)一個(gè)mDNS客戶端有咨,并開始向本地網(wǎng)絡(luò)中發(fā)送查詢報(bào)文琐簇。當(dāng)發(fā)現(xiàn)可用的服務(wù)時(shí),客戶端將回調(diào)一個(gè)提供服務(wù)信息的回調(diào)函數(shù)座享,以便應(yīng)用程序可以處理這些信息婉商。通過(guò)這種方式,應(yīng)用程序可以在本地網(wǎng)絡(luò)中發(fā)現(xiàn)可用的服務(wù)征讲,并使用這些服務(wù)進(jìn)行網(wǎng)絡(luò)通信据某。