欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

前言
在应用开发中,网络连通性检测是一个强需求。
- 用户的网络是 WiFi 还是 4G?
- 虽然连着 WiFi,但是否真的能通公网(Ping www.baidu.com)?
- 连接内网服务器的延迟是多少?
虽然 connectivity_plus 可以告诉我们网络类型(WiFi/Mobile),但它无法检测实际的连通性(比如 WiFi 连上了但没网)。这时,最直接的手段就是 Ping。
dart_ping 是一个跨平台的 Dart Ping 库。它并不重新实现 ICMP 协议(那需要 root 权限),而是巧妙地封装了各操作系统的原生 ping 命令,并解析其输出流。
对于 OpenHarmony,虽然其底层基于 Linux 内核,但由于该库内部对 Platform 类型的严格检查以及应用沙箱对进程 fork 的限制,dart_ping 的标准用法目前在鸿蒙系统上会直接失效。
一、核心原理
dart_ping 的工作原理非常 Unix:
- Spawn Process : 启动一个子进程运行
ping google.com。 - Stream Output: 监听 stdout 和 stderr。
- Regex Parse : 使用正则表达式解析每一行输出(如
64 bytes from ... time=23ms)。 - Dart Object : 转换为结构化的
PingData对象供业务使用。
Ping('baidu.com')
Process.start
ping cmd
ICMP Echo Reply
stdout
正则解析
更新 UI
Flutter App
DartPing 封装
系统 Shell
鸿蒙/Linux Kernel
Stream
延迟显示
二、集成与用法详解
2.1 添加依赖
yaml
dependencies:
dart_ping: ^9.0.1
2.2 基础用法
dart
import 'package:dart_ping/dart_ping.dart';
void main() async {
// 1. 创建 Ping 实例
// count: 5 表示 ping 5 次后自动停止
final ping = Ping('www.qq.com', count: 5);
// 2. 监听流
ping.stream.listen((PingData event) {
if (event.response != null) {
print('收到回复: ${event.response!.time}ms, seq=${event.response!.seq}');
} else if (event.error != null) {
print('Ping 错误: ${event.error}');
} else if (event.summary != null) {
print('统计: 发送 ${event.summary!.transmitted}, 接收 ${event.summary!.received}');
}
});
// 也可以手动通过 ping.stop() 停止无线 ping
}

2.3 鸿蒙的权限与环境封锁
在 OpenHarmony 应用沙箱中,应用层开发者通常会面临以下两个致命障碍:
- 库平台识别失败 :
dart_ping内部会检查Platform.isAndroid或Platform.isIOS。由于 OpenHarmony 返回的是自己的平台 ID,库会直接抛出:
UnimplementedError: Ping not supported on this platform - 进程 fork 被禁 :即便绕过库直接调用
Process.start('ping', ...),也会因为沙箱隔离无法访问系统的/bin/ping二进制文件,从而抛出:
ProcessException: No such file or directory
结论:在标准鸿蒙 HAP 开发中,依赖系统 ping 命令的方案是不可行的。
3.2 替代方案:TCP 与 HTTP 探测(推荐)
既然 ICMP 受限,我们应该使用 Dart 原语提供的网络能力来实现类似的连通性检测。
A. TCP 握手检测 (TCP Ping)
这是最通用的方式,不依赖系统底层命令,在模拟器和真机上均百分之百可用。
dart
import 'dart:io';
Future<bool> tcpPing(String host, int port, {Duration timeout = const Duration(seconds: 2)}) async {
try {
// 尝试建立连接,成功响应即代表服务器可达
final socket = await Socket.connect(host, port, timeout: timeout);
socket.destroy();
return true;
} catch (e) {
print('TCP Connect Failed: $e');
return false;
}
}

B. HTTP HEAD 请求
如果你检测的是 Web 服务器,发送一个 HEAD 请求(不下载包体)是非常节省流量且专业的方式。
dart
import 'package:http/http.dart' as http;
Future<bool> httpCheck(String url) async {
try {
// 只请求响应头,不下载内容,速度极快
final response = await http.head(Uri.parse(url)).timeout(Duration(seconds: 2));
return response.statusCode < 400; // 2xx 或 3xx 均代表服务存活
} catch (e) {
return false;
}
}

3.3 如果你有权限:适配 Ping 解析器
dart_ping 允许自定义解析器。如果鸿蒙的 ping 命令输出格式与标准 Linux 不同(例如这是 BusyBox 修改版),你需要注册自定义解析器。
dart
class OhosPingParser extends PingParser {
// 实现正则解析逻辑
@override
StreamTransformer<String, PingData> get transform => ...
}
final ping = Ping('1.1.1.1', parser: OhosPingParser());
3.4 网络权限配置
无论使用哪种方式,必须在 module.json5 中申请网络权限:
json5
"requestPermissions": [
{
"name": "ohos.permission.INTERNET"
}
]
四、功能延伸:网络诊断工具
结合 dart_ping,我们可以开发一个鸿蒙版的"网络诊断助手"。
- DNS 解析 :使用
InternetAddress.lookup。 - HTTP 连通性 :使用
dio或http。 - ICMP 延迟 :使用
dart_ping(如果环境支持)。
dart
class NetworkDiagnostics {
// 综合诊断
static Stream<String> diagnose(String target) async* {
yield '开始诊断 $target...';
// 1. DNS
try {
var addresses = await InternetAddress.lookup(target);
yield 'DNS 解析成功: ${addresses.first.address}';
} catch (e) {
yield 'DNS 解析失败';
return;
}
// 2. Ping
yield '正在 Ping...';
await for (var data in Ping(target, count: 3).stream) {
if (data.response != null) {
yield 'Ping: ${data.response!.time}ms';
}
}
}
}
五、总结
dart_ping 是一个非常方便的 wrapper 库,它隔离了底层的 Process 操作细节。
对于 OpenHarmony 开发者:
- 优先使用 TCP/HTTP :在鸿蒙 HAP 应用中,直接调用系统
ping命令由于沙箱隔离和库兼容性问题,通常会抛出UnimplementedError。 - 网络连通性 ≠ ICMP:对于绝大多数业务场景,TCP 探测和 HTTP HEAD 请求已经足够,且在权限管理上更加稳健。
它不仅能用于简单的连通性检查,还能用于计算网络抖动(Jitter)和丢包率,是网络敏感型应用(如实时音视频、游戏加速器)的重要辅助工具。
最佳实践:
- 不要在主线程运行:Ping 是异步的 Stream,不要阻塞 UI。
- 设置超时:务必处理超时逻辑,避免 Ping 进程僵死。
- 用户隐私:Ping 用户的内网地址可能涉及隐私,请确保合规。
六、完整实战示例
dart
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:dart_ping/dart_ping.dart';
class PingManagerExample extends StatefulWidget {
const PingManagerExample({super.key});
@override
State<PingManagerExample> createState() => _PingManagerExampleState();
}
class _PingManagerExampleState extends State<PingManagerExample> {
final TextEditingController _hostController =
TextEditingController(text: 'www.baidu.com');
final List<String> _logs = [];
bool _isPinging = false;
Ping? _ping;
bool _isAborted = false;
void _startPing() {
if (_isPinging) return;
_isAborted = false;
setState(() {
_isPinging = true;
_logs.clear();
_logs.add('🔍 正在启动诊断: ${_hostController.text}...');
});
try {
// 尝试使用标准的 ICMP Ping (dart_ping 库)
_ping = Ping(_hostController.text, count: 5, timeout: 2, interval: 1);
_ping!.stream.listen((event) {
if (!mounted) return;
setState(() {
if (event.response != null) {
final time = event.response!.time?.inMilliseconds ?? 0;
_logs
.add('✅ ICMP Reply: time=${time}ms seq=${event.response!.seq}');
} else if (event.error != null) {
_logs.add('❌ ICMP Error: ${event.error}');
} else if (event.summary != null) {
final sum = event.summary!;
_logs.add(
'📊 统计: 发送 ${sum.transmitted}, 接收 ${sum.received}, 丢包率 ${((sum.transmitted - sum.received) / sum.transmitted * 100).toStringAsFixed(1)}%');
}
});
}, onDone: () {
if (mounted) {
setState(() => _isPinging = false);
_logs.add('🏁 ICMP 诊断结束');
}
}, onError: (e) {
// 如果流内部抛出"平台不支持"的错误
if (e.toString().contains('UnimplementedError')) {
_runTcpFallback();
} else {
_handleError('流错误: $e');
}
});
} on UnimplementedError {
// 捕获同步抛出的平台不支持错误
_runTcpFallback();
} catch (e) {
_handleError('启动失败: $e');
}
}
// TCP 降级方案:当 ICMP 不可用时调用
void _runTcpFallback() async {
setState(() {
_logs.add('⚠️ 系统禁止 ICMP Ping (如 OpenHarmony 沙箱限制)');
_logs.add('🔄 正在尝试 [TCP 仿真] 降级方案 (Port:80)...');
});
int transmitted = 5;
int received = 0;
final host = _hostController.text;
for (int i = 0; i < transmitted; i++) {
if (_isAborted || !mounted) break;
final stopwatch = Stopwatch()..start();
try {
final socket =
await Socket.connect(host, 80, timeout: const Duration(seconds: 2));
stopwatch.stop();
socket.destroy();
received++;
if (mounted) {
setState(() {
_logs.add(
'✅ TCP Connect: $host:80, time=${stopwatch.elapsedMilliseconds}ms');
});
}
} catch (e) {
if (mounted) {
setState(() => _logs.add('❌ TCP Failed: $e'));
}
}
await Future.delayed(const Duration(seconds: 1));
}
if (mounted) {
setState(() {
_isPinging = false;
_logs.add(
'📊 TCP 统计: 发送 $transmitted, 成功 $received, 成功率 ${(received / transmitted * 100).toStringAsFixed(0)}%');
_logs.add('🏁 降级诊断结束');
});
}
}
void _handleError(String msg) {
if (mounted) {
setState(() {
_isPinging = false;
_logs.add('🚫 $msg');
});
}
}
void _stopPing() {
_isAborted = true;
_ping?.stop();
setState(() => _isPinging = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('6.1 全功能网络诊断助手')),
body: Column(
children: [
Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Expanded(
child: TextField(
controller: _hostController,
decoration: const InputDecoration(
labelText: '目标主机',
hintText: '域名或IP',
border: OutlineInputBorder(),
),
),
),
const SizedBox(width: 10),
if (_isPinging)
IconButton(
icon: const Icon(Icons.stop_circle,
color: Colors.red, size: 40),
onPressed: _stopPing,
)
else
IconButton(
icon: const Icon(Icons.play_circle,
color: Colors.blue, size: 40),
onPressed: _startPing,
),
],
),
),
const Divider(height: 1),
Expanded(
child: Container(
color: const Color(0xFF1E1E1E),
width: double.infinity,
padding: const EdgeInsets.all(8),
child: ListView.builder(
itemCount: _logs.length,
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 2),
child: Text(
_logs[index],
style: const TextStyle(
color: Colors.lightGreenAccent,
fontFamily: 'monospace',
fontSize: 13,
height: 1.4,
),
),
);
},
),
),
),
],
),
);
}
}
