从"能 ping 通吗"到"为什么上不了网"——我写了一个网络故障诊断引擎

从"能 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...

相关推荐
徐小夕3 小时前
jitword 协同文档3.2发布:打造浏览器中最强word编辑器
前端·架构·github
齐翊5 小时前
分享一个在 Claude Code 里 [同时] 用多个 ApiKey 的方法
程序员·github·agent
A_Lonely_Cat5 小时前
记一次 GitHub 幽灵协作者大清洗:强制重写 Git 历史与穿透 CDN 缓存实践
git·github
极光技术熊1 天前
Spring AI 从入门到精通:构建你的 AI 开发知识体系
后端·github
用户39483951075531 天前
怎么让我的 Agent 真正"懂"我?——关于记忆、经验学习与预测的一些真实体验
github
远航_1 天前
git submodule
前端·后端·github
fthux1 天前
如果你用 Mac,那你可能需要 Noti Shift
macos·开源·github
程序员天天困2 天前
Loop Engineering 实战:/goal 命令让 AI 自己写完整项目
github
徐小夕2 天前
我们开源了一款“框架无关”的思维导图编辑器,3分钟集成到任意系统
前端·javascript·github