从"能 ping 通吗"到"为什么上不了网"------我写了一个网络故障诊断引擎
前言
"你 ping 一下百度看看通不通?"
这是每个搞计算机的人都说过的话。但 ping 只是告诉你通不通,它不会告诉你为什么不通 ,更不会给你解决方案。
计算机网络期末答辩,我决定做一个不一样的网络诊断工具------它会像医生一样推理病因、给出处方。从检测到诊断,从数据到决策。
项目地址:GitHub - network-doctor | 仅 2 个 PyPI 依赖
设计理念:从检测到诊断
传统的网络工具(ping、tracert、nslookup)都在做同一件事------告诉你数据:
markdown
> ping 8.8.8.8
请求超时。
然后呢?你得自己猜原因------是网线松了?是 DNS 挂了?还是运营商欠费了?
Network Doctor 多走了一步:把数据变成诊断。
markdown
🔴 健康评分: 35 分
📋 诊断结果: DNS 解析故障
🔍 可能原因:
1. DNS 服务器配置错误
2. DNS 缓存污染
3. 路由器 DNS 代理异常
💡 解决建议:
1. 将 DNS 修改为 114.114.114.114
2. 执行 ipconfig /flushdns 清除缓存
3. 重启路由器或检查路由器 DNS 设置
这个推理过程由一个规则引擎完成------这是整个项目最核心的部分。
系统架构
lua
浏览器 (Bootstrap 5 + SSE)
|
| HTTP / Server-Sent Events (实时进度推送)
v
Flask 应用工厂
|
+-- 路由层 (Blueprint)
| +-- /diagnose 诊断页面
| +-- /history 历史记录
| +-- /api/* REST API + SSE 流
|
+-- 服务层
| +-- network_checker 5 项网络检测
| +-- diagnose_engine 规则推理引擎 ⭐
| +-- report_generator 中文 PDF 报告
|
+-- 数据层
+-- SQLite (WAL 模式, 24 列诊断快照)
技术选型
| 层 | 技术 | 选择理由 |
|---|---|---|
| Web 框架 | Flask | 轻量,适合小型工具项目 |
| 数据库 | SQLite | 零配置,适合本地应用 |
| 前端 | Bootstrap 5 + 原生 JS | CDN 引入,零构建步骤 |
| 实时推送 | Server-Sent Events | 比 WebSocket 轻量,单向推送够用 |
| PDF 导出 | fpdf2 | 纯 Python,不依赖系统 PDF 库 |
| 依赖总数 | 2 个 | flask + fpdf2 |
核心实现:规则推理引擎
这是整个项目最有价值的部分。5 条规则按优先级从高到低匹配,命中即停:
python
# diagnose_engine.py --- 规则匹配引擎
RULES = [
{
"priority": 1,
"id": "no_ip",
"condition": lambda r: not r["network_info"]["local_ip"]
or r["network_info"]["local_ip"] == "127.0.0.1",
"diagnosis": {
"cause": "本机未获取到有效的 IP 地址",
"severity": "critical",
"reasons": [
"网卡未启用或驱动异常",
"DHCP 服务未分配地址",
"网线未连接或端口故障"
],
"suggestions": [
"检查网线是否插好,交换机/路由器是否通电",
"打开网络设置,检查网卡是否被禁用",
"执行 ipconfig /renew 重新获取 IP 地址",
"检查网卡驱动是否正常(设备管理器)",
"尝试更换网线或端口"
]
}
},
{
"priority": 2,
"id": "gateway_fail",
"condition": lambda r: not r["checks"]["gateway"]["success"],
"diagnosis": {
"cause": "无法连接到网关(路由器)",
"severity": "error",
"reasons": [
"路由器断电或故障",
"网线连接松动或断开",
"交换机端口故障"
],
"suggestions": [
"检查路由器电源指示灯是否正常",
"重新插拔路由器与电脑之间的网线",
"登录路由器管理页面检查运行状态",
"尝试用其他设备连接同一网络确认问题范围",
"重启路由器(拔电源等待 30 秒后重新插上)"
]
}
},
# ... DNS 故障、外网故障、网站故障 依次匹配
]
健康评分算法
python
def calculate_health_score(checks):
"""0-100 分,四个维度各 25 分"""
score = 0
# 网关: 25 分
if checks["gateway"]["success"]:
score += 25
elif checks["gateway"].get("loss_rate", 100) < 50:
score += 10 # 丢包但没全丢,给部分分
# DNS: 25 分
if checks["dns"]["success"]:
score += 25
elif checks["dns"].get("resolved_count", 0) > 0:
score += 10 # 部分解析成功
# 外网: 25 分
if checks["internet"]["success"]:
score += 25
# 网站: 25 分
if checks["website"]["success"]:
score += 25
return min(score, 100)
为什么这样设计?因为网络诊断有分层模型:
- 网关不通 → 局域网问题,跟 DNS 无关
- DNS 不通 → 域名解析问题,跟外网无关
- 外网不通 → ISP 侧问题,跟网站无关
规则引擎按这个逻辑链自上而下匹配,第一级命中就直接出结果,不往下查------就像医生先看呼吸道再看肺部。
另一亮点:跨平台系统编程
Ping 输出解析------中英文通吃
Windows 中文版的 ping 输出是"平均 = 3ms",英文版是"Average = 3ms"。为了兼容两种环境,正则写得比较灵活:
python
def _ping(host, count=2, timeout=3):
# Windows: ping -n COUNT -w TIMEOUT_MS HOST
# Linux: ping -c COUNT -W TIMEOUT HOST
cmd = ['ping', '-n', str(count), '-w', str(timeout * 1000), host]
result = subprocess.run(cmd, capture_output=True, text=True, encoding='gbk')
output = result.stdout
# 丢包率
loss_match = re.search(r'(\d+)%', output)
loss_rate = int(loss_match.group(1)) if loss_match else 100
# 平均延迟------同时匹配中文和英文
avg_match = re.search(r'平均\s*=\s*(\d+)ms', output) # 中文
if not avg_match:
avg_match = re.search(r'Average\s*=\s*(\d+)ms', output) # 英文
return {
'success': result.returncode == 0 and loss_rate < 100,
'loss_rate': loss_rate,
'avg_ms': int(avg_match.group(1)) if avg_match else None
}
ipconfig 解析------也是中英文双模式
python
# 匹配中文 "IPv4 地址" 和英文 "IPv4 Address"
ip_match = re.search(r'IPv4[^\d]*:\s*([\d.]+)', line)
这个 [^\d]* 是关键------它吃掉"地址"或"Address"这些非数字字符,只提取后面的 IP。
SSE 实时进度推送
诊断过程不是瞬间完成的(有意的延迟让用户看到每一步),所以用 Server-Sent Events 推送进度:
css
data: {"step": "info", "name": "获取本机网络信息", "status": "completed"}
data: {"step": "gateway", "name": "检测网关连通性", "status": "running"}
data: {"step": "gateway", "name": "检测网关连通性", "status": "completed"}
data: {"step": "dns", "name": "检测 DNS 解析", "status": "running"}
...
data: {"step": "complete", "result": {...完整诊断结果...}}
前端用原生 EventSource 接收,降级方案是 fetch POST:
javascript
// 优先 SSE,降级 fetch
function startDiagnosis() {
const eventSource = new EventSource('/api/diagnose/stream');
eventSource.onmessage = (e) => {
const data = JSON.parse(e.data);
if (data.step === 'complete') {
renderResult(data.result); // 完整诊断结果渲染
eventSource.close();
} else {
updateProgress(data); // 更新步骤指示器
}
};
eventSource.onerror = () => {
// SSE 失败,降级为传统 POST
eventSource.close();
fetch('/api/diagnose', { method: 'POST' })
.then(r => r.json())
.then(renderResult);
};
}
答辩现场,进度条一步一步走完再弹出完整结果------这个体验比直接出结果好太多。
PDF 报告导出
用 fpdf2 生成中英文混排的诊断报告。关键是中文字体自动发现:
python
class DiagnosisReport(FPDF):
def _find_chinese_font(self):
"""在 Windows 系统中搜索中文字体"""
candidates = [
"C:/Windows/Fonts/msyh.ttc", # 微软雅黑
"C:/Windows/Fonts/simsun.ttc", # 宋体
"C:/Windows/Fonts/simhei.ttf", # 黑体
]
for path in candidates:
if os.path.exists(path):
return path
return None # 降级为 Helvetica(仅 ASCII)
报告结构:标题 + 健康评分(大字彩色) + 本机信息表 + 4 项检测结果 + 诊断结论 + 编号建议列表。
总结
这个项目从设计到实现花了一天时间。代码量不大,但有几个值得分享的思考:
1. "诊断"比"检测"有价值
工具告诉用户"ping 不通",只是一个事实 。诊断引擎告诉用户"网关不通,请检查路由器电源",是一个决策。从事实到决策,是工程师和产品经理的分界线。
2. 规则引擎适合小规模推理
5 条规则的 if-else 在工程上看起来"不够高级",但它可解释、可调试、可维护。如果是 500 条规则再考虑推理机------但在那之前,规则引擎就是最好的选择。
3. SSE 是 WebSocket 的轻量替代
诊断过程是单向的------服务器推送进度给浏览器,不需要双向通信。SSE 比 WebSocket 简单得多,原生 EventSource 浏览器支持好。很多场景根本不需要 WebSocket。
4. 课堂项目也要认真对待文档
README、设计文档、答辩介绍 ------ 这些不是"做完项目再补的",而是项目的一部分。设计文档帮你在编码前理清思路,答辩介绍帮你在演示时逻辑清晰。
源码已开源,欢迎 Star ⭐
GitHub: github.com/nemor-ii/ne...