VC++局域网扫描工具ScanLan:ARP+ICMP实现30秒扫完255台设备
简介这是一份面向网络管理初学者与运维人员的VC局域网扫描工具源码核心功能是快速发现同一网段内所有主机的IP与MAC地址适用于网络设备盘点、IP冲突排查、未授权设备识别及DHCP环境下的地址核对等场景。压缩包共15个文件以h头文件与cpp源文件为主辅以rc、rc2资源脚本、ico图标、dsw与dsp工程文件及clw类向导文件整体仅13KB结构紧凑可直接用Visual C打开编译运行。据描述该工具借助ARP或ICMP协议实现扫描约30秒即可遍历一个网段的255台机器效率较高。目前已有733人学习下载读者可从中获取完整的工程目录组织方式、扫描逻辑实现思路与网络协议调用范例适合作为网络编程入门练手或二次开发的基础模板。1. 30 秒扫完 255 台ScanLan 到底解决了什么局域网痛点上周帮朋友排查办公室网络路由器后台只显示 12 台设备但实际工位上明明坐了 20 多号人还有几台打印机和监控 NVR 没算进去。这种「设备在跑但管理界面看不见」的情况在局域网里太常见了——DHCP 租约过期没释放、设备用了静态 IP 绕开分配、或者干脆是有人私接了小路由。想搞清楚一个网段里到底挂了哪些机子、每台对应什么 MAC 地址靠 ping 一台台试根本不现实255 个地址扫一遍手动操作得半小时起步。ScanLan 就是冲着这个场景来的。它是一套用 VC 写的局域网扫描工具核心逻辑是通过发送 ARP 请求和 ICMP 探测把同一网段内所有活跃主机的 IP 和 MAC 地址一次性拉出来。压缩包里是完整的 MFC 工程源码包含ScanLanByIPCDlg.cpp主对话框逻辑、IPCSendFile.cpp进程间通信模块、ScanLanByIPC.dsp工程文件以及资源脚本和图标。拿到手可以直接用 VC6 或 VS 打开编译也能在源码基础上改扫描策略。适合谁用网络管理员做资产盘点、安全人员查未授权设备、开发人员想研究 ARP 扫描的底层实现或者单纯想搞明白「局域网里到底有多少台机子」的人。它不依赖任何外部库纯 Win32 API 加 MFC编译出来就是一个独立 exe扔到任意 Windows 机器上就能跑。下面从 ARP 原理讲到编译配置再到实际扫描时容易翻车的地方一步步拆开说。2. ARP 与 ICMP 双管齐下扫描逻辑与源码结构拆解2.1 为什么单靠 ping 扫不全局域网很多人第一反应是用ping 192.168.1.1-254循环扫一遍但这里有个血泪经验Windows 防火墙默认会丢弃 ICMP 回显请求很多机器 ping 不通但实际在线。更关键的是ping 只能告诉你「这个 IP 有响应」拿不到 MAC 地址——而 MAC 地址才是二层网络里设备的唯一身份证。ScanLan 的做法是 ARP 为主、ICMP 为辅。ARP 协议工作在数据链路层当你向一个 IP 发送 ARP 请求时只要目标在线且在同一广播域内它就必须回复自己的 MAC 地址。这个过程不经过 IP 层防火墙过滤所以比 ping 可靠得多。源码里ScanLanByIPCDlg.cpp的扫描线程就是先构造 ARP 请求包通过SendARP()API 或者原始套接字发送出去然后解析回复报文里的 MAC 信息。那 ICMP 什么时候用当目标设备跨网段或者 ARP 缓存里没有记录时先发一个 ICMP 探测包「唤醒」一下再发 ARP 请求命中率会更高。源码里IPCSendFile.cpp负责的就是这部分进程间通信和辅助探测逻辑虽然名字带 IPC但实际承担了部分数据包组装和发送的活。2.2 源码文件分工与关键函数定位拿到压缩包解压后文件结构不算复杂但第一次看 MFC 工程容易懵。我把核心文件的作用列一下方便你直接定位到要改的地方文件名作用改哪里ScanLanByIPCDlg.cpp主对话框逻辑扫描线程入口扫描范围、超时时间ScanLanByIPCDlg.h对话框类声明新增成员变量IPCSendFile.cpp数据包组装与发送ARP 包结构、重试次数ScanLanByIPC.cpp应用初始化一般不用动ScanLanByIPC.dspVC6 工程文件编译配置Resource.h资源 ID 定义新增控件时改关键函数在ScanLanByIPCDlg.cpp里扫描线程通常叫ScanThreadProc或者类似名字。它会遍历你指定的 IP 段对每个 IP 调用SendARP()。这个 API 是 Windows 自带的原型是DWORD SendARP(IPAddr DestIP, IPAddr SrcIP, PULONG pMacAddr, PULONG PhyAddrLen)返回 NO_ERROR 就说明拿到了 MAC。源码里应该做了并发或者批量发送的优化不然 255 个地址串行发 ARP 不可能 30 秒扫完。2.3 编译环境搭建与工程配置这套代码是 VC6 时代的产物用高版本 VS 打开会提示工程升级。我一般直接上 VS2019 或 VS2022让它自动转换然后改两个地方就能编过。第一步用 VS 打开ScanLanByIPC.dsw或ScanLanByIPC.dsp弹出升级向导点「确定」。升级后检查项目属性把字符集改成「使用多字节字符集」因为 MFC 老代码默认不是 Unicode。第二步在stdafx.h里确认包含了winsock2.h和iphlpapi.hSendARP函数需要链接Iphlpapi.lib。如果编译报LNK2019找不到SendARP就在项目属性 → 链接器 → 输入 → 附加依赖项里加上Iphlpapi.lib。// stdafx.h 中确保包含以下头文件 #include winsock2.h #include iphlpapi.h #pragma comment(lib, Iphlpapi.lib) // 自动链接省去手动配置 #pragma comment(lib, ws2_32.lib) // 如果用了原始套接字上面这段是常见做法#pragma comment比在项目属性里手动加库方便换台机器也不用重新配。ws2_32.lib是如果你要改写成原始套接字发 ARP 才需要原版用SendARP的话只加Iphlpapi.lib就够。第三步如果报error C4996: inet_ntoa: Use inet_ntop() or InetNtop() instead在项目属性 → C/C → 预处理器 → 预处理器定义里加_WINSOCK_DEPRECATED_NO_WARNINGS。这是 VS 高版本对老 API 的安全检查加个宏就绕过不影响功能。编译成功后会在 Debug 或 Release 目录下生成ScanLanByIPC.exe双击就能跑。界面一般是 MFC 对话框上面有个 IP 段输入框和「开始扫描」按钮扫完结果用 ListCtrl 展示 IP、MAC 和状态。3. 从输入网段到导出结果一次完整扫描的操作链路3.1 设置扫描范围与超时参数程序跑起来后第一件事是填对 IP 段。常见格式是192.168.1.0到192.168.1.255或者直接填192.168.1.1-254。源码里解析这个范围的逻辑在ScanLanByIPCDlg.cpp的OnBnClickedScan()里它会把你输入的字符串拆成起始和结束 IP。超时时间这个参数很关键。默认可能是 100ms 或 200ms意思是发一个 ARP 请求后等多久没回复就跳过。局域网内正常延迟在 1-5ms100ms 足够。但如果网络里有老设备或者无线桥接响应可能到 50ms 以上这时候超时设太短会漏掉设备。我一般把超时设成 300ms255 个地址并发扫也就 30 秒左右和标题说的「30 秒扫完」对得上。// 在扫描线程里设置超时和重试的典型写法 #define SCAN_TIMEOUT_MS 300 // 单次 ARP 等待超时 #define MAX_RETRY 2 // 失败重试次数 for (int i startIP; i endIP; i) { ULONG macAddr[2] {0}; ULONG macLen 6; DWORD ret SendARP(htonl(i), 0, macAddr, macLen); if (ret NO_ERROR) { // 解析 MAC 地址并添加到结果列表 BYTE* mac (BYTE*)macAddr; CString strMac; strMac.Format(_T(%02X-%02X-%02X-%02X-%02X-%02X), mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); m_ListCtrl.InsertItem(0, inet_ntoa(*(in_addr*)i)); m_ListCtrl.SetItemText(0, 1, strMac); } // 失败时可选重试 }这段代码展示了核心扫描循环。SendARP第一个参数是目标 IP注意要用htonl转成网络字节序。macAddr是输出缓冲区macLen传入时告诉 API 缓冲区大小返回时是实际 MAC 长度。拿到 MAC 后按%02X格式化成常见的AA-BB-CC-DD-EE-FF形式。重试逻辑可以根据需要加但注意重试会增加总耗时255 个地址每个重试 2 次最坏情况时间翻三倍。3.2 扫描结果解读与 MAC 地址厂商识别扫完之后列表里会出现三类结果有 IP 有 MAC 的、有 IP 无 MAC 的、以及完全没出现的。有 MAC 的说明设备在线且 ARP 回复正常。有 IP 无 MAC 的情况比较少见通常是设备刚下线或者 ARP 缓存过期。完全没出现的就是不在线或者被防火墙拦了 ARP。MAC 地址前三个字节是 OUI组织唯一标识符能看出设备厂商。比如00-1A-2B开头可能是某品牌网卡B8-27-EB是树莓派基金会。源码里没有内置 OUI 数据库但你可以自己加一个映射表把常见厂商前缀写进去扫完直接显示「Apple」「Intel」「TP-Link」这种可读名称。// 简单的 OUI 厂商映射示例 struct OUIInfo { CString prefix; CString vendor; }; OUIInfo ouiTable[] { {_T(00-1A-2B), _T(某厂商A)}, {_T(B8-27-EB), _T(树莓派)}, {_T(00-50-56), _T(VMware虚拟网卡)}, // 可以继续补充 };这个表不用太全覆盖你网络里常见的设备类型就行。VMware 的00-50-56和 VirtualBox 的08-00-27特别有用一眼就能区分物理机和虚拟机。3.3 结果导出与二次处理MFC 的 ListCtrl 支持直接保存成文本或 CSV。源码里可能没带导出功能但加一个不难。在对话框上放个「导出」按钮响应函数里遍历 ListCtrl 的每一行用CStdioFile写出去。void CScanLanByIPCDlg::OnBnClickedExport() { CFileDialog dlg(FALSE, _T(csv), _T(scan_result.csv), OFN_OVERWRITEPROMPT, _T(CSV Files (*.csv)|*.csv||)); if (dlg.DoModal() ! IDOK) return; CStdioFile file; if (!file.Open(dlg.GetPathName(), CFile::modeCreate | CFile::modeWrite)) return; file.WriteString(_T(IP,MAC,Status\n)); int count m_ListCtrl.GetItemCount(); for (int i 0; i count; i) { CString ip m_ListCtrl.GetItemText(i, 0); CString mac m_ListCtrl.GetItemText(i, 1); CString line; line.Format(_T(%s,%s,Online\n), ip, mac); file.WriteString(line); } file.Close(); }导出成 CSV 后可以直接扔进 Excel 做资产表或者用脚本比对历史记录看哪些 MAC 是新出现的。CStdioFile写中文路径没问题但注意如果 MAC 地址里有逗号分隔符要处理一下这里用的是短横线所以安全。4. 避坑与排查扫描不到、编译报错、结果异常怎么办4.1 编译时提示 SendARP 未定义或链接失败现象VS 里编译报error C3861: SendARP: identifier not found或者LNK2019: unresolved external symbol _SendARP16。原因SendARP声明在iphlpapi.h里实现库是Iphlpapi.lib。老 VC6 工程默认没包含这个头文件和库升级到高版本 VS 后也不会自动加。解决在stdafx.h里加#include iphlpapi.h和#pragma comment(lib, Iphlpapi.lib)。如果还报错检查项目属性 → 链接器 → 输入 → 附加依赖项里有没有手动加过Iphlpapi.lib有的话删掉避免重复。另外确认平台是 Win32 而不是 x64老 MFC 代码在 x64 下可能有指针长度问题。4.2 扫描结果里大量设备显示「无 MAC」现象列表里 IP 出现了但 MAC 列是空的或者显示00-00-00-00-00-00。原因三种可能。一是目标设备开启了防火墙的 ARP 防护不回复 ARP 请求二是跨网段了ARP 广播到不了三是超时设太短设备还没回复程序就跳过了。解决先确认扫描机和目标在同一网段子网掩码一致。然后把超时从 100ms 调到 300ms 甚至 500ms。如果还不行试试先用ping目标 IP 一次让 ARP 缓存建立起来再跑扫描。源码里如果用的是SendARP它依赖系统 ARP 缓存先 ping 一下能显著提高命中率。4.3 扫描速度远慢于 30 秒现象255 个地址扫了三四分钟还没完。原因源码里可能是串行发送每个 IP 等超时再发下一个。255 乘以 300ms 就是 76 秒加上重试更久。解决改成并发发送。用CreateThread开多个线程每个线程负责一段 IP或者用IOCP做异步。简单做法是开 10 个线程每个扫 25 个地址总时间就降到 10 秒以内。但注意SendARP本身是线程安全的多线程调用没问题。改的时候在ScanLanByIPCDlg.cpp里把单线程循环拆成线程池模式。4.4 扫到了不认识的设备 MAC现象结果里出现陌生 MAC但 IP 不在你预期的 DHCP 范围里。原因有人私接设备或者用了静态 IP。也可能是虚拟机网卡桥接到了物理网络。解决先查 OUI 前缀确认厂商。如果是00-50-56或08-00-27基本是 VMware 或 VirtualBox。如果是随机 MAC第一个字节的第二位是 1比如02-xx-xx可能是手机开了随机 MAC 地址功能。把这些 MAC 记下来去交换机上查对应端口或者用arp -a在核心交换机上看 ARP 表定位到具体接入位置。4.5 在 Win10/Win11 上运行报兼容性错误现象exe 双击没反应或者提示「此应用无法在你的电脑上运行」。原因VC6 编译出来的 exe 依赖老版本 MFC 运行库新系统可能没装。解决用 VS2019/2022 重新编译生成 Release 版本然后在项目属性 → 高级 → 字符集改成多字节。如果还不行在 exe 上右键 → 属性 → 兼容性 → 勾选「以兼容模式运行这个程序」选 Windows 7。但最好的办法还是用新 VS 重编一劳永逸。5. 进阶改造把 ScanLan 变成持续资产监控工具原版 ScanLan 是一次性扫描扫完就完了。但实际网络管理里设备是动态变化的——今天多台笔记本明天少台打印机。我后来在源码基础上加了个定时扫描和差异对比的功能用起来顺手很多。思路很简单在对话框上加个「定时扫描」复选框和间隔输入框用SetTimer每隔 N 分钟触发一次扫描。每次扫描结果存到一个std::mapCString, CString里key 是 MACvalue 是 IP。下次扫描时对比两个 map新增的 MAC 标绿消失的标红。这样一眼就能看出网络里多了什么少了什么。// 差异对比的核心逻辑 std::mapCString, CString m_lastScan; // 上次扫描结果 MAC-IP std::mapCString, CString m_currScan; // 本次扫描结果 void CompareScanResult() { // 找出新增设备 for (auto pair : m_currScan) { if (m_lastScan.find(pair.first) m_lastScan.end()) { // 新增 MAC在列表里标绿 int idx m_ListCtrl.InsertItem(0, pair.second); m_ListCtrl.SetItemText(idx, 1, pair.first); m_ListCtrl.SetItemText(idx, 2, _T(新增)); m_ListCtrl.SetItemData(idx, RGB(0, 128, 0)); // 绿色 } } // 找出消失设备 for (auto pair : m_lastScan) { if (m_currScan.find(pair.first) m_currScan.end()) { // 消失 MAC标红 int idx m_ListCtrl.InsertItem(0, pair.second); m_ListCtrl.SetItemText(idx, 1, pair.first); m_ListCtrl.SetItemText(idx, 2, _T(离线)); m_ListCtrl.SetItemData(idx, RGB(255, 0, 0)); // 红色 } } m_lastScan m_currScan; }这段代码里m_lastScan和m_currScan分别存上次和本次的 MAC-IP 映射。遍历对比后新增的用绿色显示消失的用红色显示。SetItemData存颜色值然后在NM_CUSTOMDRAW消息里根据这个值设置文字颜色。这样每次定时扫描完列表自动刷新你扫一眼就知道网络有没有异常变动。验证方法也简单找一台手机连上 WiFi 扫一次记下 MAC。然后手机关 WiFi再扫一次看列表里那台设备是不是变红了。再开 WiFi扫第三次看是不是变绿。这个流程走一遍差异对比逻辑就验证完了。从那以后我每次接手一个新网络都先跑一遍全量扫描存基线然后开定时对比。有次就是靠这个发现一台私接的树莓派在半夜偷偷跑流量MAC 前缀B8-27-EB直接暴露了厂商。希望帮到你。本文还有配套的精品资源点击获取
上一篇/下一篇内容由系统自动关联
返回资讯列表 →