Flutter Windows端UDP设备发现功能异常排查与修复求助
问题描述
我正在开发一款通过UDP数据包实现设备互发现的Flutter应用,当前遇到以下问题:
- Android设备间可正常互发现
- Windows设备无法发现其他设备,但Android设备能发现Windows设备
- 已尝试关闭Windows防火墙并添加例外、关闭第三方杀毒软件,问题仍未解决
需求:实现Windows与Android设备的UDP双向互发现,以下是我的代码实现,求解决方法及调试方案。
import 'dart:async'; import 'dart:convert'; import 'dart:io'; // Only for non-Web platforms import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; import 'package:network_info_plus/network_info_plus.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'file_select_page.dart'; // Ensure to import file_select_page class TransferPage extends StatefulWidget { const TransferPage({Key? key}) : super(key: key); @override _TransferPageState createState() => _TransferPageState(); } class _TransferPageState extends State<TransferPage> { List<Map<String, String>> devices = []; bool isScanning = false; late RawDatagramSocket receiver; Timer? scanTimer; @override void initState() { super.initState(); startListening(); scanNetwork(); // Start scanning on initialization } Future<void> startListening() async { receiver = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 22473); receiver.listen((RawSocketEvent event) async { if (event == RawSocketEvent.read) { Datagram? datagram = receiver.receive(); if (datagram != null) { _handleReceivedData(datagram); } } }); } void _handleReceivedData(Datagram datagram) async { try { final message = utf8.decode(datagram.data, allowMalformed: true); final ip = datagram.address.address; if (ip != await _getCurrentIP()) { final parts = message.split(';'); String deviceBrand = parts.length > 1 ? parts[1].replaceFirst('Brand:', '') : 'Unknown Brand'; String deviceModel = parts.length > 2 ? parts[2].replaceFirst('Model:', '') : 'Unknown Model'; // Deduplication check if (!devices.any((device) => device['ip'] == ip)) { setState(() { devices.add({ 'ip': ip, 'port': '22473', 'status': 'Responded', 'brand': deviceBrand, 'model': deviceModel }); }); } } } catch (e) { print('Failed to decode data: $e'); // Handle decode failure } } Future<void> scanNetwork() async { setState(() { isScanning = true; devices.clear(); // Clear device list for new scan }); if (kIsWeb) { // Logic for Web platform return; // Broadcasting not supported on Windows for now } else { // Get current IP on non-Web platforms (like Windows) String? wifiIP = await _getCurrentIP(); if (wifiIP == null) { setState(() { isScanning = false; }); return; } String broadcastAddress = await getBroadcastAddress(wifiIP); // Start periodic broadcasting Timer.periodic(Duration(seconds: 1), (timer) async { await sendBroadcast(broadcastAddress); // Use dynamically calculated broadcast address }); // Set scan timeout scanTimer = Timer(Duration(seconds: 5), stopScanning); } } Future<void> sendBroadcast(String address) async { String message; // Use Platform property only on non-Web platforms if (Platform.isWindows) { message = 'Hello from Windows;Brand:Windows;Model:PC'; } else { String deviceBrand = await _getDeviceBrand(); String deviceModel = await _getDeviceModel(); message = 'Hello from Flutter;Brand:$deviceBrand;Model:$deviceModel'; } RawDatagramSocket sender = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 0); sender.send(utf8.encode(message), InternetAddress(address), 22473); // Send to specified address sender.close(); } Future<String?> _getCurrentIP() async { final info = NetworkInfo(); return await info.getWifiIP(); } Future<String> _getDeviceBrand() async { DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); String brand = "Unknown Brand"; if (Platform.isAndroid) { AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; brand = androidInfo.brand!; } else if (Platform.isIOS) { IosDeviceInfo iosInfo = await deviceInfo.iosInfo; brand = iosInfo.utsname.machine!; } return brand; } Future<String> _getDeviceModel() async { DeviceInfoPlugin deviceInfo = DeviceInfoPlugin(); String model = "Unknown Model"; if (Platform.isAndroid) { AndroidDeviceInfo androidInfo = await deviceInfo.androidInfo; model = androidInfo.model!; } else if (Platform.isIOS) { IosDeviceInfo iosInfo = await deviceInfo.iosInfo; model = iosInfo.utsname.machine!; } return model; } Future<String> getBroadcastAddress(String ip) async { List<String> parts = ip.split('.'); return '${parts[0]}.${parts[1]}.${parts[2]}.255'; // Assuming subnet mask is 255.255.255.0 } void stopScanning() { setState(() { isScanning = false; }); scanTimer?.cancel(); } @override void dispose() { receiver.close(); scanTimer?.cancel(); super.dispose(); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar( title: const Text('File Transfer'), actions: [ IconButton( icon: const Icon(Icons.refresh), onPressed: () { scanNetwork(); }, ), ], ), body: isScanning ? Center(child: CircularProgressIndicator()) : ListView.builder( itemCount: devices.length, itemBuilder: (context, index) { return GestureDetector( onTap: () { // Navigate to file_select_page on tapping device Navigator.push( context, MaterialPageRoute( builder: (context) => TransferFilesPage( deviceName: devices[index]['brand'] ?? 'Unknown Brand', deviceIp: devices[index]['ip'] ?? 'Unknown IP', ), ), ); }, child: Card( margin: const EdgeInsets.all(10.0), child: Padding( padding: const EdgeInsets.all(16.0), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ Text(devices[index]['ip'] ?? 'Unknown IP'), Text('Port: 22473'), Text('Status: ${devices[index]['status'] ?? 'Unknown'}'), Text('Brand: ${devices[index]['brand'] ?? 'Unknown Brand'}'), Text('Model: ${devices[index]['model'] ?? 'Unknown Model'}'), ], ), ], ), ), ), ); }, ), ); } }
问题分析与修复方案
核心问题排查
- 广播地址计算不准确:当前代码硬编码假设子网掩码为255.255.255.0,但Windows设备可能处于非标准子网环境,导致广播包无法覆盖整个局域网。
- Windows UDP接收权限缺失:默认绑定
InternetAddress.anyIPv4可能未启用广播接收权限,导致无法捕获其他设备的广播包。 - 自过滤逻辑错误:通过IP判断是否为自身数据包的逻辑,在Windows多网卡环境下(如有线、无线、虚拟机网卡)会误判,过滤掉其他设备的正常数据包。
具体修复步骤
1. 修复广播地址计算逻辑
替换硬编码的广播地址生成方式,通过真实子网掩码计算正确的广播地址:
Future<String> getBroadcastAddress(String ip) async { final interfaces = await NetworkInterface.list(type: InternetAddressType.IPv4, includeLinkLocal: true); for (var interface in interfaces) { for (var addr in interface.addresses) { if (addr.address == ip) { // 通过IP和子网掩码计算广播地址 List<int> ipBytes = InternetAddress(ip).rawAddress; List<int> maskBytes = addr.netmask != null ? InternetAddress(addr.netmask!).rawAddress : [255,255,255,0]; List<int> broadcastBytes = []; for (int i=0; i<4; i++) { broadcastBytes.add(ipBytes[i] | (~maskBytes[i] & 0xFF)); } return InternetAddress.fromRawAddress(Uint8List.fromList(broadcastBytes)).address; } } } // fallback到原逻辑 List<String> parts = ip.split('.'); return '${parts[0]}.${parts[1]}.${parts[2]}.255'; }
2. 启用Windows端UDP广播接收
在startListening中添加广播组加入配置,确保能接收局域网广播包:
Future<void> startListening() async { receiver = await RawDatagramSocket.bind(InternetAddress.anyIPv4, 22473); // 启用广播接收权限 receiver.setOption( RawSocketOption(IPPROTO_IP, IP_ADD_MEMBERSHIP, InternetAddress('224.0.0.0').rawAddress + InternetAddress.anyIPv4.rawAddress) ); receiver.listen((RawSocketEvent event) async { if (event == RawSocketEvent.read) { Datagram? datagram = receiver.receive(); if (datagram != null) { _handleReceivedData(datagram); } } }); }
3. 修改自过滤逻辑
放弃IP对比,通过数据包内容判断是否为自身发送的消息:
void _handleReceivedData(Datagram datagram) async { try { final message = utf8.decode(datagram.data, allowMalformed: true); final ip = datagram.address.address; // 通过消息前缀判断是否为自身发送的包 bool isSelfMessage = false; if (Platform.isWindows) { isSelfMessage = message.startsWith('Hello from Windows'); } else { String deviceBrand = await _getDeviceBrand(); isSelfMessage = message.startsWith('Hello from Flutter;Brand:$deviceBrand'); } if (!isSelfMessage) { final parts = message.split(';'); String deviceBrand = parts.length > 1 ? parts[1].replaceFirst('Brand:', '') : 'Unknown Brand'; String deviceModel = parts.length > 2 ? parts[2].replaceFirst('Model:', '') : 'Unknown Model'; if (!devices.any((device) => device['ip'] == ip)) { setState(() { devices.add({ 'ip': ip, 'port': '22473', 'status': 'Responded', 'brand': deviceBrand, 'model': deviceModel }); }); } } } catch (e) { print('Failed to decode data: $e'); } }
调试方案
- 抓包验证:在Windows上用Wireshark监听UDP端口22473,确认是否收到Android设备的广播包。若未收到,检查广播地址是否正确;若收到但代码未处理,检查过滤逻辑。
- 日志输出:在
_handleReceivedData中添加详细日志,打印收到的IP、消息内容,确认数据包是否被错误过滤。 - 多网卡检查:打印Windows所有网卡的IP地址,确认
_getCurrentIP()返回的是当前连接WiFi的IP,避免多网卡导致的逻辑错误。
内容的提问来源于stack exchange,提问作者danmo
相关产品推荐
相关产品推荐

