在局域网开发场景中,经常需要知道当前网段内有哪些设备在线,比如智能家居调试、内网设备管理面板等。arp协议正好承担了这个职责:它通过IP与MAC地址的映射关系,让我们可以快速探测局域网内的存活设备。本文将使用Node.js实现arp扫描功能,并结合Mock2Image思路,把扫描结果转成可视化的模拟图片输出,方便前端联调时直接使用。

arp协议基础与Node.js调用方式
arp(Address Resolution Protocol,地址解析协议)工作在网络层与数据链路层之间,负责把IP地址解析成MAC地址。当一台主机需要和同网段另一台主机通信时,会先发送arp请求广播,目标设备收到后回复自己的MAC地址,双方再将映射关系缓存到本地arp表中。
在Node.js中我们并不需要从零构造arp报文,直接读取操作系统的arp缓存表即可。Windows下可以使用arp -a命令,Linux下则可以读取/proc/net/arp文件或执行同样的命令。Node.js的child_process.exec模块可以很方便地执行系统命令并拿到输出结果。
const { exec } = require('child_process');
function getArpTable() {
return new Promise((resolve, reject) => {
exec('arp -a', { encoding: 'utf8' }, (err, stdout) => {
if (err) return reject(err);
resolve(stdout);
});
});
}
getArpTable().then(table => {
console.log(table);
});上面这段代码在Windows与Linux下都能运行,区别在于输出格式不同。Windows输出为表格形式,Linux输出为多行文本。因此解析时需要做兼容处理,这也是下面实现解析器时要重点解决的问题。
实现arp扫描与解析
单纯读取arp缓存只能拿到最近通信过的设备,如果需要扫描整个网段,可以先对每个IP发起一次轻量连接(比如TCP连接网关或常见端口),触发系统写入arp缓存,然后再统一解析缓存表。下面给出完整实现。
const net = require('net');
const { exec } = require('child_process');
// 触发arp记录:尝试与目标IP的80端口建立TCP连接
function touchHost(ip) {
return new Promise(resolve => {
const socket = new net.Socket();
socket.setTimeout(800);
socket.once('connect', () => { socket.destroy(); resolve(true); });
socket.once('timeout', () => { socket.destroy(); resolve(false); });
socket.once('error', () => { socket.destroy(); resolve(false); });
socket.connect(80, ip);
});
}
// 解析arp -a 输出,兼容Windows和Linux
function parseArpOutput(output) {
const devices = [];
const lines = output.split('\n');
for (const line of lines) {
// 匹配 IP 和 MAC 地址
const ipMac = line.match(/(\d{1,3}(?:\.\d{1,3}){3})\s+.*?([0-9a-fA-F]{2}(?::[0-9a-fA-F]{2}){5})/);
if (ipMac) {
devices.push({ ip: ipMac[1], mac: ipMac[2].toLowerCase() });
}
}
return devices;
}
// 扫描指定网段
async function scanSubnet(base, from, to) {
for (let i = from; i <= to; i++) {
await touchHost(`${base}.${i}`);
}
return new Promise((resolve, reject) => {
exec('arp -a', { encoding: 'utf8' }, (err, stdout) => {
if (err) return reject(err);
resolve(parseArpOutput(stdout));
});
});
}
scanSubnet('192.168.1', 1, 254).then(devices => {
console.log(`发现 ${devices.length} 台设备`);
console.log(devices);
});解析函数中使用的正则表达式同时兼容了Windows的短横线分隔MAC和Linux的冒号分隔MAC格式,匹配到后统一转成小写便于后续比较。需要注意的是,扫描是串行发起的,254个地址大约需要几十秒,如果追求速度可以用Promise.all并发执行,但要控制并发数量,避免瞬时创建过多socket拖垮系统。
另一个细节是超时时间的设置。800毫秒对于局域网已经足够,如果网络环境较差可以适当调大。判定设备在线的标准是它出现在arp表中,因为只要TCP握手发起,系统就会完成arp解析,即使端口未开放,arp记录同样会生成。
Mock2Image:把扫描结果渲染成占位图片
拿到设备列表后,下一步是Mock2Image的核心思路:把数据模拟成图片。这在前后端联调时特别有用,前端需要一个返回设备图片的接口,但真实设备可能不在线,此时可以直接用Node.js动态生成带有设备信息的PNG图片作为响应。
生成图片不依赖第三方库也可以做到,但手写PNG编码比较繁琐,推荐使用svg2png思路:先用SVG描述图片内容,再转成PNG。这里为了演示清晰,直接用HTTP接口返回SVG,大多数浏览器都支持直接渲染SVG图片。
const http = require('http');
function buildDeviceSvg(device) {
const label = `${device.ip} / ${device.mac}`;
return `<svg xmlns="http://www.w3.org/2000/svg" width="400" height="200">
<rect width="100%" height="100%" fill="#2d3436"/>
<text x="200" y="90" font-size="20" fill="#dfe6e9" text-anchor="middle">Device Mock</text>
<text x="200" y="130" font-size="16" fill="#74b9ff" text-anchor="middle">${label}</text>
</svg>`;
}
http.createServer((req, res) => {
const device = { ip: '192.168.1.105', mac: 'aa:bb:cc:dd:ee:ff' };
res.writeHead(200, { 'Content-Type': 'image/svg+xml' });
res.end(buildDeviceSvg(device));
}).listen(3000, () => {
console.log('Mock image server running at http://127.0.0.1:3000');
});前端只需要把<img>标签的src指向这个接口地址,就能拿到一张带设备IP与MAC信息的模拟图片。如果确实需要PNG格式,可以引入sharp库把SVG转换成PNGBuffer后再输出,代码只需增加几行。
工程化建议与注意事项
把扫描和图片服务整合成一个完整工具时,建议注意以下几点。第一,arp命令需要系统权限正常执行,某些精简版Linux容器中可能没有arp命令,此时应改为直接读取/proc/net/arp文件。第二,扫描结果建议加缓存,避免每次请求都触发全网段扫描,可以设置一个TTL,比如60秒内复用上次结果。
第三,如果这个服务会暴露给外部访问,一定要做IP白名单限制,因为arp表暴露了内网设备信息,属于敏感数据。第四,在Mock场景下可以预置一批虚拟设备数据,扫描不到真实设备时自动降级到Mock数据,保证前端页面始终有内容可渲染,这也是Mock2Image方案最大的价值所在:环境无关、开箱即用。
整套方案代码量不大,核心难点只在arp输出的兼容解析和扫描触发机制上。理解了这两点,就可以根据实际业务扩展出设备指纹识别、上下线告警等更复杂的功能。
Node.jsarp扫描Mock2Image修改时间:2026-08-31 04:00:38