简介这是一份面向C#初学者与Windows网络工具开发爱好者的中文可视化Ping工具实现方案专为不熟悉英文命令行输出的用户设计将传统CMD中晦涩的Ping结果转化为直观易懂的中文界面降低网络连通性检测门槛。资源包共15个文件含6个核心C#源码文件如Form1.cs、Program.cs、2个本地化资源文件.resx、2个说明类文本含功能介绍与使用指引、1个Visual Studio解决方案.sln及配套项目配置文件.csproj、.suo、.settings整体仅14KB轻量易读。已有467人学习下载适合快速理解System.Net.NetworkInformation命名空间下的Ping类封装、WinForms界面响应逻辑、多线程异步执行与UI更新机制。源码结构清晰无冗余依赖可直接编译运行是掌握网络诊断工具开发与中文化界面实践的典型入门范例。1. 一个能看懂的 ping 工具不是命令行里敲ping baidu.com然后等一串英文回显而是点开就显示「正在连接百度服务器…延迟 28ms通畅」——这是给一线运维、教学演示、嵌入式调试甚至家长帮孩子查网络卡不卡用的 C# 中文可视化 PING 命令程序你有没有试过让非技术人员比如刚接手学校机房的老师、产线设备维护员、或者家里老人去判断“网络是不是断了”他们打开 CMD输ping www.taobao.com看到Reply from 110.75.144.36: bytes32 time42ms TTL52这一行根本不知道TTL52是啥更分不清Request timed out和Destination host unreachable的区别。而这个项目就是把 Windows 原生ping.exe的能力用 C# 封装成一个带图标、有状态栏、支持中文提示、可暂停/重连、自动记录历史、还能导出 CSV 的桌面程序。它不替代高级网络诊断但解决了「第一眼判断通不通、快不快、稳不稳」这个高频刚需。适合三类人直接下载跑起来教计算机基础课的老师拖拽 IP 就能课堂演示、工业现场做设备联网验收的技术员不用记命令点选预设地址一键测通断、以及想学 .NET 桌面开发的新手——它结构干净、无第三方 UI 框架依赖、所有逻辑都在PingManager.cs和主窗体里改两行就能接入自己的设备列表。2. 技术栈与工程结构基于 .NET Framework 4.7.2 的 WinForms 项目零 NuGet 依赖纯原生 System.Net.NetworkInformation 实现这个项目不是调用Process.Start(ping.exe)然后解析命令行输出的“假可视化”而是真正在 C# 层调用System.Net.NetworkInformation.Ping类完成 ICMP 请求再把结果映射到 UI 控件上。整个解决方案只有 1 个.sln文件、1 个 WinForms 项目.csproj没有 WPF、没有 Avalonia、没用任何第三方 UI 库——这意味着你双击.sln就能在 Visual Studio 2019 里直接编译不需要额外安装 SDK 或配置运行时。项目目录结构极简ChinesePingTool/ ├── ChinesePingTool.sln ├── ChinesePingTool/ │ ├── Program.cs // 主入口设置高 DPI 感知和启动窗体 │ ├── MainForm.cs // 主窗体含地址输入框、开始/暂停按钮、实时延迟图表、历史记录 ListView │ ├── MainForm.Designer.cs // 自动设计器文件控件布局 │ ├── PingManager.cs // 核心类封装 Ping 发送、超时控制、结果解析、事件通知 │ ├── PingResult.cs // 数据模型包含 IP、响应时间、TTL、状态Success/Timeout/Fail、时间戳 │ ├── HistoryRecord.cs // 历史记录实体含序列化为 CSV 所需属性 │ └── Resources/ // 图标、语言资源目前仅中文提示项目默认目标框架是.NET Framework 4.7.2不是 .NET Core 或 .NET 5。这是因为System.Net.NetworkInformation.Ping在早期 .NET Framework 中对 ICMPv4 支持最稳定且无需管理员权限即可发送 ping在 .NET Core 3.1 中需开启ICMP权限Windows 上常被防火墙拦截。如果你强行升级到 .NET 6会遇到Ping.SendAsync返回Access Denied错误——这不是代码 bug是运行时权限模型变更导致的后面「避坑章节」会细说。2.1 主窗体MainForm.cs的关键控件与事件流主窗体采用标准 WinForms 设计核心交互逻辑集中在三个事件处理中btnStart_Click触发 PingManager.Start()禁用输入框和按钮启用暂停按钮btnPause_Click调用 PingManager.Pause()切换按钮文本为「继续」PingManager.OnPingCompleted事件回调在 UI 线程更新延迟图表、状态标签、历史列表。其中延迟图表并非第三方控件而是用PanelGraphics手绘的简易折线图DrawLatencyChart()方法每收到一次成功响应就在 Panel 上绘制一个点并连接前一点。X 轴为时间序号从 1 开始Y 轴为毫秒值最大显示 60 个点超出则左移。这种实现牺牲了美观但完全避免了引用ZedGraph或OxyPlot等重量级图表库——对于一个只显示最近 60 次延迟的工具够用且可控。// MainForm.cs 片段手绘延迟图表的核心逻辑 private void DrawLatencyChart(Listint latencies) { if (latencies.Count 0) return; using (var g pnlChart.CreateGraphics()) { g.Clear(Color.White); var width pnlChart.Width; var height pnlChart.Height; var margin new Padding(20, 10, 10, 20); var chartWidth width - margin.Left - margin.Right; var chartHeight height - margin.Top - margin.Bottom; // 找出最大延迟用于 Y 轴缩放最小为 0最大取最近 60 次中的 max 或 500ms防图表拉平 int maxY Math.Max(latencies.TakeLast(60).Max(), 100); for (int i 1; i latencies.Count i 60; i) { int x1 margin.Left (i - 1) * chartWidth / 59; int y1 height - margin.Bottom - (int)((double)latencies[i - 1] / maxY * chartHeight); int x2 margin.Left i * chartWidth / 59; int y2 height - margin.Bottom - (int)((double)latencies[i] / maxY * chartHeight); using (var pen new Pen(Color.FromArgb(30, 144, 255), 2)) g.DrawLine(pen, x1, y1, x2, y2); } } }这段代码的关键参数说明chartWidth / 59将 X 轴均分为 59 段对应 60 个点0→59保证首尾对齐y height - margin.Bottom - (value / maxY * chartHeight)Y 坐标从下往上增长WinForms 坐标系原点在左上角所以用height - bottom - value实现“倒置”Math.Max(..., 100)防止所有延迟都低于 10ms 时图表压缩成一条线强制最低 Y 轴范围为 100ms保障可视性。2.2 核心类 PingManager.cs如何用 C# 原生 API 发送 ICMP 包并规避常见陷阱PingManager.cs是整个项目的中枢它不继承任何基类仅依赖System.Net和System.Threading.Tasks。其设计遵循「单例 事件驱动」模式对外暴露Start(string host, int timeoutMs 1000, int intervalMs 1000)和Pause()方法内部用CancellationTokenSource控制异步任务生命周期。关键实现细节在于SendPingAsync方法// PingManager.cs 片段真正的 ICMP 发送逻辑 private async TaskPingResult SendPingAsync(string host, int timeoutMs) { using (var ping new Ping()) { try { // 构造 32 字节 ICMP 载荷兼容老设备 byte[] buffer new byte[32]; new Random().NextBytes(buffer); // 发送异步请求超时由 CancellationToken 控制非 PingOptions.Timeout var cts new CancellationTokenSource(timeoutMs); var reply await ping.SendPingAsync(host, timeoutMs, buffer, new PingOptions(64, true), cts.Token); return new PingResult { Host host, Status reply.Status IPStatus.Success ? Success : reply.Status.ToString(), RoundtripTime reply.RoundtripTime, Ttl reply.Options?.Ttl ?? 0, Timestamp DateTime.Now, ErrorMessage string.Empty }; } catch (PingException ex) { return new PingResult { Host host, Status PingException, RoundtripTime -1, Ttl 0, Timestamp DateTime.Now, ErrorMessage ex.Message }; } catch (OperationCanceledException) { return new PingResult { Host host, Status Timeout, RoundtripTime -1, Ttl 0, Timestamp DateTime.Now, ErrorMessage $请求在 {timeoutMs}ms 内未收到响应 }; } catch (Exception ex) { return new PingResult { Host host, Status UnknownError, RoundtripTime -1, Ttl 0, Timestamp DateTime.Now, ErrorMessage ex.GetType().Name : ex.Message }; } } }这段代码的参数与逻辑说明buffer new byte[32]显式指定载荷长度避免某些嵌入式设备如部分 PLC、工控网关因载荷过长默认 32 字节或过短0 字节拒绝响应new PingOptions(64, true)第一个参数是 TTL64 是通用值Linux 默认 64Windows 默认 128第二个true表示不分片Dont Fragment确保包在网络中不被拆分提升跨设备兼容性cts.Token传入SendPingAsync这是关键Ping.SendPingAsync的超时机制依赖CancellationToken而不是PingOptions.Timeout后者仅影响 ICMP 包头的 TTL 字段不控制 C# 层等待时间。若只设PingOptions.Timeout 1000实际等待可能长达数秒四层catch分类捕获区分PingException网络层错误如目标不可达、OperationCanceledException超时、其他异常DNS 解析失败、空主机名等确保每种失败都有明确状态码和中文提示。2.3 中文本地化与状态映射为什么IPStatus.Success不直接显示英文项目没有使用.resx多语言资源而是在PingResult.cs中硬编码中文状态映射因为目标场景是纯中文环境且状态枚举有限共 12 种IPStatus值实际常用仅 4 种。PingResult.StatusText属性返回如下映射IPStatus 值中文显示适用场景Success✅ 延迟 24ms正常响应TimedOut⏳ 请求超时目标关机、防火墙拦截、路由黑洞DestinationHostUnreachable❌ 目标主机不可达本地网络断开、网关失效BadOption⚠️ 参数错误输入非法 IP如 999.999.999.999这个映射表直接写在PingResult.StatusText的 getter 里无配置文件、无反射、无资源加载——启动快、无 IO、不怕部署时缺.resources文件。例如// PingResult.cs 片段轻量级中文状态映射 public string StatusText { get { return Status switch { Success $✅ 延迟 {RoundtripTime}ms, Timeout ⏳ 请求超时, DestinationHostUnreachable ❌ 目标主机不可达, TimedOut ⏳ 请求超时, BadIpAddress ⚠️ IP 地址格式错误, UnknownError $⚠️ {ErrorMessage}, _ $❓ {Status} }; } }这种写法牺牲了扩展性但换来的是 100% 可控的中文输出——不会因系统区域设置如 Windows 设置为英文导致状态栏显示Success也不会因资源加载失败变成空白。对于一个给终端用户看的工具确定性比灵活性重要得多。3. 编译与运行Visual Studio 2019 一键生成无需安装运行时exe 可直接拷贝到 Win7 机器运行这个项目编译产物是一个独立的.exe文件约 120KB不依赖 .NET Framework 以外的任何 DLL。它的运行环境要求非常宽松Windows 7 SP1 及以上已预装 .NET Framework 4.7.2Win10 1803 默认自带Win7 需手动安装 KB4074904 补丁包约 70MB微软官网可下载。3.1 从源码到可执行文件的完整步骤VS2019 环境确认开发环境打开 Visual Studio 2019社区版免费确保已安装「.NET desktop development」工作负载加载解决方案双击ChinesePingTool.slnVS 自动识别为 .NET Framework 项目检查目标框架右键项目 → Properties → Application → Target framework 应为.NET Framework 4.7.2若显示 4.8请手动改回 4.7.2否则可能因Ping类行为差异导致超时不准清理并重建菜单栏 Build → Clean Solution再 Build → Rebuild Solution定位输出文件编译成功后在ChinesePingTool\bin\Release\目录下找到ChinesePingTool.exe首次运行验证双击该 exe输入127.0.0.1点击「开始」应立即看到状态栏变为 ✅ 延迟 XXms图表出现第一个点。注意若编译报错The type or namespace name Ping does not exist in the namespace System.Net.NetworkInformation说明项目未正确引用System.Net程序集。右键项目 → Add Reference → 勾选System.Net注意不是System.Net.Http→ 点确定。这是 VS 有时自动丢失引用的玄学问题重装 SDK 无效必须手动补。3.2 无 VS 环境下的快速运行方案使用 .NET SDK 命令行编译如果你只有 .NET SDK如dotnet-sdk-6.0.400-win-x64.exe也可以跳过 VS用命令行编译。但注意必须使用 .NET SDK 6.0.x 版本且需手动降级目标框架因为 SDK 7 默认创建 .NET 7 项目而本项目是 .NET Framework。# 步骤 1进入项目根目录 cd ChinesePingTool # 步骤 2修改 csproj 文件将 TargetFramework 改为 net472原内容可能是 net6.0-windows # 用记事本打开 ChinesePingTool.csproj找到 TargetFrameworknet6.0-windows/TargetFramework # 替换为 TargetFrameworknet472/TargetFramework # 步骤 3恢复 NuGet 包虽然本项目无 NuGet但 SDK 会检查 dotnet restore # 步骤 4发布为自包含应用含运行时体积大但免安装 dotnet publish -c Release -r win-x64 --self-contained true -p:PublishTrimmedtrue # 输出路径bin\Release\net472\win-x64\publish\ChinesePingTool.exe此方法生成的 exe 体积约 80MB含 .NET Runtime但好处是拷贝到任何 Win7 机器都能直接运行无需预装 .NET Framework。适合给不懂技术的同事打包交付。3.3 运行时权限与防火墙适配为什么有时显示「请求超时」但 CMD 的 ping 却成功这是一个高频翻车点。现象程序里 ping192.168.1.1显示 ⏳ 请求超时但 CMD 里ping 192.168.1.1返回正常响应。原因有二UAC 权限隔离WinForms 程序默认以普通用户权限运行而System.Net.NetworkInformation.Ping在某些 Windows 版本尤其是 Win10 20H2 后对 ICMP 请求做了更严格的沙箱限制。CMD 的ping.exe是系统内置工具拥有更高信任等级。防火墙规则冲突Windows Defender 防火墙可能为ChinesePingTool.exe创建了单独的出站规则默认阻止 ICMPv4 流量。解决方法右键ChinesePingTool.exe→ Properties → Compatibility → 勾选「Run this program as an administrator」或更稳妥地在代码中添加管理员权限声明打开app.manifest文件若不存在则右键项目 → Add → New Item → Application Manifest File将requestedExecutionLevel行改为requestedExecutionLevel levelrequireAdministrator uiAccessfalse /这样每次启动都会弹 UAC 提示但 ICMP 请求 100% 可达。提示不要试图用Process.Start(ping.exe)绕过此问题。虽然能跑通但会丢失PingResult.Ttl、PingResult.Options等关键字段且无法控制超时精度CMD ping 的-w参数最小为 1000ms而 C#Ping可精确到 1ms。4. 避坑指南5 个真实踩过的坑每个都附带复现步骤、根因分析和一行修复代码这些坑全部来自某高校实验室部署该工具时的真实血泪经验不是理论推测。它们分散在不同模块但共同特点是现象诡异、日志无报错、Google 搜索不到直接答案。4.1 坑输入www.baidu.com能通但输入baidu.com就超时 —— DNS 解析失败静默吞掉现象在地址栏输入baidu.com点击开始状态栏一直显示 ⏳ 请求超时Fiddler 抓包发现无任何 DNS 请求发出换成www.baidu.com则立刻成功。原因Ping.SendPingAsync内部调用Dns.GetHostAddressesAsync而baidu.com作为裸域名在某些网络环境下如企业内网 DNS 未配置泛解析会返回System.Net.Sockets.SocketException: No such host is known但该异常被PingManager.SendPingAsync的catch (Exception ex)捕获后仅记录UnknownError未在 UI 显示具体 DNS 错误信息。解决在SendPingAsync的catch (Exception ex)块中增加对SocketException的特判并提取ex.SocketErrorCodecatch (SocketException sx) { string dnsMsg sx.SocketErrorCode switch { SocketError.HostNotFound 域名解析失败请检查输入是否正确或尝试加 www 前缀, SocketError.TryAgain DNS 服务器暂时不可用请稍后重试, _ $DNS 错误{sx.Message} }; return new PingResult { /* ... */, ErrorMessage dnsMsg }; }4.2 坑连续点击「开始」两次程序崩溃报ObjectDisposedException现象快速连点「开始」按钮 2 次程序闪退错误信息Cannot access a disposed object. Object name: Ping。原因PingManager.Start()每次都新建Ping实例但前一次的Ping对象未被及时释放。当第二次Start()执行时第一次的Ping可能仍在后台线程中处理响应此时using (var ping new Ping())的Dispose()被提前触发导致后续reply访问已释放对象。解决在PingManager类中添加_currentPing字段确保同一时刻只有一个Ping实例private Ping _currentPing; private async TaskPingResult SendPingAsync(string host, int timeoutMs) { // 复用现有 Ping 实例避免频繁创建销毁 _currentPing ?? new Ping(); // ... 后续逻辑不变 } // 在 Pause() 和 Stop() 中调用 _currentPing?.Dispose(); _currentPing null;4.3 坑在高 DPI 显示器如 200% 缩放上按钮文字被截断图表显示错位现象在 4K 屏 200% 缩放的笔记本上「开始」按钮只显示「开…」延迟图表高度压缩成一条线。原因WinForms 默认不启用 DPI 感知所有坐标和字体大小按 100% 缩放计算导致控件布局错乱。解决在Program.cs的Main方法顶部添加 DPI 感知声明必须在Application.Run()之前[STAThread] static void Main() { // 关键启用 Per-Monitor DPI 感知Win10 1607 if (Environment.OSVersion.Version new Version(10, 0, 14393)) SetProcessDpiAwarenessContext(new IntPtr(-3)); // DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2 Application.EnableVisualStyles(); Application.SetCompatibleTextRenderingDefault(false); Application.Run(new MainForm()); } [DllImport(user32.dll)] private static extern bool SetProcessDpiAwarenessContext(IntPtr value);4.4 坑导出 CSV 历史记录后Excel 打开显示乱码中文变问号现象点击「导出历史」生成history.csv用记事本打开正常但 Excel 2016 打开全为乱码。原因File.WriteAllText(path, csvContent)默认用 UTF-8 无 BOM 编码而旧版 Excel2016 及以前默认用系统 ANSI 编码如 GBK读取 CSV导致中文解码失败。解决强制写入 UTF-8 with BOM// 替换原来的 File.WriteAllText File.WriteAllText(path, csvContent, new UTF8Encoding(true)); // true 表示写入 BOM4.5 坑程序最小化到托盘后ping 任务停止CPU 占用率归零现象勾选「最小化到托盘」点击最小化按钮状态栏停止刷新延迟图表冻结任务管理器显示进程 CPU 占用为 0%。原因WinForms 窗体最小化时Application.Idle事件被抑制而本项目用Task.Delay(intervalMs)实现轮询当 UI 线程空闲被系统挂起时Delay不会准时唤醒。解决改用System.Threading.Timer替代await Task.DelayTimer 在后台线程运行不受窗体状态影响private Timer _pingTimer; public void Start(string host, int timeoutMs 1000, int intervalMs 1000) { _pingTimer new Timer(_ SendPingAndNotify(host, timeoutMs), null, 0, intervalMs); }5. 二次开发实战3 个高频改造需求附完整代码与验证方法现在你已经能跑通、修好坑、理解核心逻辑。接下来是真正落地的部分怎么把它变成你自己的工具我挑了三个最常被问到的需求——不是“如何加皮肤”而是解决真实工作场景的硬需求。每个都给出可直接粘贴的代码、测试步骤、以及我踩过的验证陷阱。5.1 需求支持批量 ping 多个地址如 192.168.1.1~192.168.1.254并高亮显示不通的 IP这是某公司产线设备联网验收的刚需。他们有 50 台新到的传感器每台固定 IP需要 5 分钟内确认哪些没接上网线。原程序只能单地址轮询效率太低。改造思路不改PingManager新增BatchPingManager类用Parallel.ForEach并发发送上限 10 个并发结果存入ConcurrentDictionarystring, PingResultUI 用DataGridView展示。关键代码新建BatchPingManager.cspublic class BatchPingManager { private readonly ConcurrentDictionarystring, PingResult _results new(); private readonly Liststring _targets new(); private readonly int _timeoutMs; private readonly int _maxDegreeOfParallelism; public BatchPingManager(IEnumerablestring targets, int timeoutMs 1000, int maxParallel 10) { _targets.AddRange(targets); _timeoutMs timeoutMs; _maxDegreeOfParallelism maxParallel; } public async Task RunAsync() { var options new ParallelOptions { MaxDegreeOfParallelism _maxDegreeOfParallelism }; await Task.Run(() { Parallel.ForEach(_targets, options, target { var result PingSingle(target, _timeoutMs); _results[target] result; }); }); } private PingResult PingSingle(string host, int timeoutMs) { using var ping new Ping(); try { var reply ping.Send(host, timeoutMs); return new PingResult { Host host, Status reply.Status IPStatus.Success ? Success : reply.Status.ToString(), RoundtripTime reply.RoundtripTime, Ttl reply.Options?.Ttl ?? 0, Timestamp DateTime.Now }; } catch { return new PingResult { Host host, Status Failed, RoundtripTime -1 }; } } public IEnumerablePingResult GetAllResults() _results.Values; }UI 集成在MainForm.cs中添加按钮和 DataGridView// 添加按钮 click 事件 private async void btnBatchPing_Click(object sender, EventArgs e) { var ips Enumerable.Range(1, 254).Select(i $192.168.1.{i}); var batch new BatchPingManager(ips, 500, 20); // 20 并发500ms 超时 // 清空现有数据 dgvBatchResults.Rows.Clear(); // 显示「执行中」 lblBatchStatus.Text 执行中...; Application.DoEvents(); // 强制刷新 UI否则状态栏不更新 await batch.RunAsync(); // 绑定结果 foreach (var r in batch.GetAllResults()) { var rowIdx dgvBatchResults.Rows.Add(r.Host, r.StatusText, r.RoundtripTime); if (r.Status ! Success) dgvBatchResults.Rows[rowIdx].DefaultCellStyle.BackColor Color.LightCoral; // 不通的标红 } lblBatchStatus.Text $完成共 {dgvBatchResults.RowCount} 个地址; }验证方法启动程序确保局域网内有至少 1 台设备如路由器 192.168.1.1在线其余 IP 关机点击「批量 Ping」观察dgvBatchResults是否在 3 秒内填满 254 行检查192.168.1.1行是否显示 ✅ 延迟 XXms 且背景为白色其他行是否为 ❌ 目标主机不可达 且背景为浅红色陷阱提醒不要在RunAsync()中直接awaitParallel.ForEach—— 它是同步 APIawait会导致死锁。必须用Task.Run(() Parallel.ForEach(...))包一层。5.2 需求将 ping 结果实时推送至企业微信机器人实现「网络异常自动告警」这是某数据中心值班室的要求。他们需要当某个核心服务如数据库 10.0.0.100连续 3 次 ping 失败时自动发消息到企业微信告警群。改造思路监听PingManager.OnPingCompleted事件用滑动窗口记录最近 3 次结果满足条件则调用HttpClientPOST 企业微信 webhook。关键代码在MainForm.cs的构造函数中添加// 初始化滑动窗口存储最近 3 次结果 private readonly QueuePingResult _failureWindow new(3); private const int FAILURE_THRESHOLD 3; private readonly string _wechatWebhook https://qyapi.weixin.qq.com/xxx; // 替换为你的真实 webhook public MainForm() { InitializeComponent(); // 订阅 ping 完成事件 PingManager.Instance.OnPingCompleted OnPingCompleted; } private void OnPingCompleted(PingResult result) { if (result.Host 10.0.0.100 result.Status ! Success) { _failureWindow.Enqueue(result); if (_failureWindow.Count FAILURE_THRESHOLD) _failureWindow.Dequeue(); if (_failureWindow.Count FAILURE_THRESHOLD) { TriggerWechatAlert(result.Host); } } } private async void TriggerWechatAlert(string host) { var msg new { msgtype text, text new { content $ 网络告警核心服务 {host} 连续 {FAILURE_THRESHOLD} 次 ping 失败\n时间{DateTime.Now:yyyy-MM-dd HH:mm:ss} } }; using var client new HttpClient(); var json JsonSerializer.Serialize(msg); var content new StringContent(json, Encoding.UTF8, application/json); try { var response await client.PostAsync(_wechatWebhook, content); if (response.IsSuccessStatusCode) Console.WriteLine($告警已发送至企业微信{host}); else Console.WriteLine($告警发送失败HTTP {response.StatusCode}); } catch (Exception ex) { Console.WriteLine($告警发送异常{ex.Message}); } }验证方法先关闭目标机器如 10.0.0.100确保它真实不可达在程序中输入10.0.0.100点击「开始」观察控制台View → Output → Debug是否在第 3 次失败后打印告警已发送至企业微信陷阱提醒企业微信 webhook 有频率限制1分钟最多20条生产环境需加SemaphoreSlim限流此处为简化省略另外HttpClient应全局复用而非每次新建否则可能引发 socket 耗尽。5.3 需求导出报告为 PDF含图表截图和统计摘要如成功率 98.2%平均延迟 15ms这是给客户交付验收报告的刚需。原 CSV 导出太简陋客户要 PDF。改造思路用QuestPDF库轻量、MIT 协议、无依赖生成 PDF。不引入新窗体复用现有图表 Panel用DrawToBitmap截图。步骤dotnet add package QuestPDF需先dotnet new console创建新项目或直接下载 DLL 手动引用在MainForm.cs中添加导出按钮事件private void btnExportPdf_Click(object sender, EventArgs e) { var doc Document.Create(container { container.Page(page { page.Size(PageSizes.A4); page.Margin(2, Unit.Centimetre); page.Header().Element(ComposeHeader); page.Content().Element(ComposeContent); page.Footer().AlignCenter().Text($生成时间{DateTime.Now:yyyy-MM-dd HH:mm:ss}); }); }); var pdfPath Path.Combine(Application.StartupPath, $PingReport_{DateTime.Now:yyyyMMdd_HHmmss}.pdf); doc.GeneratePdf(pdfPath); MessageBox.Show($PDF 报告已生成{pdfPath}, 完成, MessageBoxButtons.OK, MessageBoxIcon.Information); } private void ComposeHeader(IContainer container) { container.AlignCenter().Text(中文可视化 PING 报告).FontSize(20).Bold(); } private void ComposeContent(IContainer container) { // 截图当前图表 var bmp new Bitmap(pnlChart.Width, pnlChart.Height); pnlChart.DrawToBitmap(bmp, new Rectangle(0, 0, bmp.Width, bmp.Height)); // 转为 QuestPDF Image using var ms new MemoryStream(); bmp.Save(ms, ImageFormat.Png); container.Image(ms.ToArray()).Width(500); // 添加统计摘要 var stats GetPingStatistics(); // 自定义方法计算成功率、平均延迟等 container.PaddingVertical(10).Column(col { col.Item().Text($ 统计摘要); col.Item().Text($• 总请求数{ p a hrefhttps://download.csdn.net/download/welcome_g/10776989 stylecolor:#ec7500;font-size:14px; 本文还有配套的精品资源点击获取 /a img altmenu-r.4af5f7ec.gif srchttps://csdnimg.cn/release/wenkucmsfe/public/img/menu-r.4af5f7ec.gif stylewidth:16px;margin-left:4px;vertical-align:text-bottom;cursor:text; /p
阅读完成 · 觉得有帮助?