一、项目简介
在局域网运维、内网设备排查场景中,批量扫描网段在线主机是高频刚需。传统单线程 Ping 扫描速度极慢,扫描一个 255 网段往往需要数分钟,无法满足高效运维需求。
本文基于MFC + 多线程技术实现一款高性能局域网 Ping 扫描工具,核心功能包含:
- 支持自定义网段(输入前三位 IP,如 192.168.1)批量扫描 0~254 主机
- 可视化 16×16 网格画布,颜色区分主机状态(灰色未扫描、绿色在线)
工具采用 MFC 对话框开发,界面轻量化、无冗余依赖,适配 Windows 全平台,适合个人学习、内网运维、网络调试使用。
二、核心技术原理
1. 多线程并发扫描
传统单线程 Ping 串行执行,逐个检测 IP,耗时极高。本项目开启255 个独立工作线程,每个线程单独负责一个 IP 的 Ping 检测,实现全网段并行扫描,将扫描耗时压缩至毫秒级。
同时采用事件驱动 + 分批等待机制,规避 Windows 单批次最大等待 64 个内核对象的限制,完美兼容 255 线程同步等待逻辑,同时通过消息循环防止界面卡死。
2. 多重Ping重试防误判
内网环境存在短暂丢包、网络波动问题,单次 Ping 检测极易出现误判。项目内置4 次重试机制,只要单次 IP Ping 连通成功,即判定主机在线,大幅提升扫描准确率,超时时间设置 300ms,适配内网网络延迟特性。
3. 自定义消息UI更新
子线程禁止直接操作 UI,项目通过自定义消息 WM_THREAD_FINISH实现线程与主线程通信。子线程扫描完成后发送消息,主线程统一更新网格颜色、在线计数,保证 UI 渲染稳定、无闪烁、无线程冲突。
4. 可视化网格绘制
通过 MFC 自绘技术实现 16×16 网格画布,对应 0~254 共 255 个 IP 地址,每个单元格独立映射一个主机。通过不同颜色区分状态:灰色(未扫描)、绿色(在线),直观展示网段设备在线状态。
三、核心功能代码解析
1. 线程参数结构体定义
封装单线程所需的全部参数,包含待扫描 IP、线程ID、扫描结果、对话框指针、线程完成事件,实现线程参数统一管理:
Plain Text // 线程参数结构 struct ThreadData { CString ip; // 待扫描IP地址 int thread_id; // 线程唯一ID BOOL thread_result; // Ping结果 TRUE=在线 FALSE=离线 MPingDlg* pDlg; // 对话框指针,用于发送消息 HANDLE hEvent; // 线程完成事件句柄 }; |
2. 核心线程扫描函数
每个线程独立执行 Ping 检测,4 次重试容错,完成后通过自定义消息通知主线程更新 UI:
Plain Text UINT ThreadFunction(LPVOID pParam) { ThreadData* pData = (ThreadData*)pParam; MyPing ping; const int SCAN_CNT = 4; // 4次重试 BOOL bPingSuccess = FALSE;
// 多重Ping重试,避免内网丢包误判 for (int i = 0; i < SCAN_CNT; i++) { PingResult result; ping.Ping(pData->ip, 300, 128, result); // 超时300ms,数据包128字节 if (result.bSuccess) { bPingSuccess = TRUE; break; } Sleep(20); }
pData->thread_result = bPingSuccess; // 发送自定义消息,更新主线程UI if (pData->pDlg != NULL) { pData->pDlg->PostMessage(WM_THREAD_FINISH, pData->thread_id, bPingSuccess ? 1 : 0); }
SetEvent(pData->hEvent); return 0; } |
3. 多线程批量创建与等待逻辑
批量创建 255 个扫描线程,采用分批等待机制规避系统限制,同时通过消息泵维持界面响应:
Plain Text // 批量创建所有扫描线程 for(int i = 0; i < NUM_THREADS; i++) { threadData[i].ip.Format("%s.%d", m_strComboIP.GetBuffer(0), i); threadData[i].thread_id = i; threadData[i].thread_result = FALSE; threadData[i].pDlg = this; hEvents[i] = CreateEvent(NULL, TRUE, FALSE, NULL); threadData[i].hEvent = hEvents[i]; pThreads[i] = AfxBeginThread(ThreadFunction, &threadData[i]); } m_strComboIP.ReleaseBuffer();
// 分批等待所有线程完成,防止界面卡死 while (1) { bool allFinished = true; for (int i = 0; i < NUM_THREADS; i += MAXIMUM_WAIT_OBJECTS) { int batchCount = min(MAXIMUM_WAIT_OBJECTS, NUM_THREADS - i); DWORD ret = WaitForMultipleObjects( batchCount, &hEvents[i], TRUE, 10 ); if (ret != WAIT_OBJECT_0) { allFinished = false; } } if (allFinished) break;
// 消息循环,刷新界面 MSG msg; while (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { TranslateMessage(&msg); DispatchMessage(&msg); } } |
4. 线程消息回调更新UI
接收子线程扫描结果,动态修改网格颜色、统计在线主机数量:
Plain Text LRESULT MPingDlg::OnThreadFinish(WPARAM wParam, LPARAM lParam) { int thread_id = (int)wParam; int result = (int)lParam; if (result) { // 在线主机标记绿色 SetCellColor(thread_id / 16, thread_id % 16, RGB(0, 255, 0)); m_iIPCount++; UpdateWindow(); UpdateCount(); } return 0; } |
5. 扫描结果导出功能
遍历网格状态,筛选在线主机 IP,批量导出为 TXT 文档,方便数据归档:
Plain Text void MPingDlg::OnBnClickedBtnExport() { CString sFilePath; CFileDialog dlg(FALSE, ".txt", "IP列表", OFN_HIDEREADONLY, "列表文件(*.txt)|*.txt||"); if(dlg.DoModal() != IDOK) return; sFilePath = dlg.GetPathName();
// 清理文件属性 DWORD FileAttribute = GetFileAttributes(sFilePath); if(FileAttribute & FILE_ATTRIBUTE_READONLY) SetFileAttributes(sFilePath, FileAttribute & ~FILE_ATTRIBUTE_READONLY);
// 写入在线IP数据 FILE* fp = fopen(sFilePath.GetBuffer(0), "wb"); int cnt = 0; if(fp) { fprintf(fp, "序号 IP地址\r\n"); for(int i = 0; i < 255; i++) { if(m_cellColors[i / 16][i % 16] == RGB(0, 255, 0)) { CString sIP; sIP.Format("%s.%d", m_strComboIP, i); fprintf(fp, "%03d %s\r\n", cnt + 1, sIP.GetBuffer(0)); cnt++; } } } fclose(fp); } |
6. 本机网段自动获取
程序启动自动读取本机 IP,解析网段前缀并填充到下拉框,无需手动输入:
Plain Text char szHostName[128] = {0}; const char* pszAddr; struct hostent* pHost; if(gethostname(szHostName, 128) == 0) { pHost = gethostbyname(szHostName); for(int i = 0; pHost != NULL && pHost->h_addr_list[i] != NULL; i++) { pszAddr = inet_ntoa(*(struct in_addr*)pHost->h_addr_list[i]); CString sIP(pszAddr); CStringArray strArray; theApp.GetSplitArrayByString(sIP, strArray, "."); if(strArray.GetSize() == 4) { CString sIPHead; sIPHead.Format("%s.%s.%s", strArray[0], strArray[1], strArray[2]); m_ctrlComboIP.AddString(sIPHead); } } } m_ctrlComboIP.SetCurSel(0); |
四、项目亮点与优化点
1. 高性能并发扫描
突破单线程扫描瓶颈,255 线程全网段并发,扫描速度提升数十倍,普通网段扫描耗时控制在 1 秒内。
2. 高容错扫描机制
4 次 Ping 重试 + 300ms 超时适配内网弱网环境,彻底解决内网偶然丢包导致的离线误判问题,扫描准确率接近 100%。
3. 流畅UI交互
采用局部重绘机制,仅更新扫描完成的单元格,避免全局刷新闪烁;独立消息泵保证扫描过程中界面可拖动、可操作,无卡死现象。
4. 人性化功能设计
自动获取本机网段、实时统计扫描数据、可视化网格状态、结果文件导出,兼顾新手易用性和运维实用性。
![]()