MFC 多线程局域网扫描工具

📅 2026/7/28 19:42:50 👁️ 阅读次数 📝 编程学习
MFC 多线程局域网扫描工具

一、项目简介

在局域网运维、内网设备排查场景中,批量扫描网段在线主机是高频刚需。传统单线程 Ping 扫描速度极慢,扫描一个 255 网段往往需要数分钟,无法满足高效运维需求。

本文基于MFC + 多线程技术实现一款高性能局域网 Ping 扫描工具,核心功能包含:

  • 支持自定义网段(输入前三位 IP,如 192.168.1)批量扫描 0~254 主机
  • 255 并发多线程扫描,大幅提升扫描效率
  • 可视化 16×16 网格画布,颜色区分主机状态(灰色未扫描、绿色在线)
  • 多重 Ping 重试机制,避免内网丢包导致的误判
  • 实时统计在线主机数量、扫描耗时
  • 支持扫描结果 TXT 文件导出,方便数据留存
  • 自动获取本机网段,无需手动输入,提升使用便捷性

工具采用 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. 人性化功能设计

自动获取本机网段、实时统计扫描数据、可视化网格状态、结果文件导出,兼顾新手易用性和运维实用性。