建议端口扫描超时设为500ms,局域网设备三次握手多在200–400ms完成;并发大时可降至300ms,避免误判;禁用1秒超时以防阻塞;arp探测优先于icmp,但需root权限且macos不支持,应fallback并控制并发防端口耗尽。

用 net.DialTimeout 扫端口,但超时设太短会漏掉真实存活主机
局域网里有些设备响应慢(比如嵌入式设备、老旧打印机),net.DialTimeout 设成 100ms 可能刚发完 SYN 就返回 timeout,实际连接早已建立。这不是代码写错了,是网络行为本身有延迟抖动。
- 建议起手设为
500 * time.Millisecond,实测多数局域网设备在 200–400ms 内完成三次握手 - 若并发量大(比如扫 /24 网段所有 IP 的 top 100 端口),把超时压到
300 * time.Millisecond更稳;再低就容易误判 - 别用
time.Second—— 单个 goroutine 等 1 秒,1000 个并发就是 1000 秒,根本不是“扫描”,是排队等结果
用 arping 探活比 ICMP ping 更可靠,但得跑 root 权限
很多终端禁 ping(比如 Windows 默认关 ICMP 回显),但只要在线,ARP 表一定可查。Go 标准库不直接支持发 ARP 包,得调系统命令或用第三方包(如 github.com/mdlayher/arp),但后者要 cgo 且 Linux-only。
- 最简方案:执行
arping -c 1 -w 100 <ip></ip>,检查 stdout 是否含"Unicast reply"或 exit code 是否为 0 - 注意
arping在 macOS 不可用,得 fallback 到ping -c 1 -W 1 <ip></ip>,但得接受漏检率上升 - 别在非特权用户下硬试——
arping会直接报"Operation not permitted",没重试逻辑的话整个探测就卡死
并发控制不当会让扫描器被系统限速甚至触发防火墙
扫一个 C 段(256 个 IP)时,如果对每个 IP 同时开 100 个端口 goroutine,峰值连接数可能破万。Linux 默认 net.ipv4.ip_local_port_range 是 32768–60999,端口不够用就会复用 TIME_WAIT,接着触发 "connect: cannot assign requested address" 错误。
- 用带缓冲的 channel 控制并发数,比如
sem := make(chan struct{}, 50),每次 go 前sem ,结束后 <code> - 别按“IP 数 × 端口数”粗暴算总 goroutine —— 实际活跃连接数取决于超时+响应速度,50 是局域网较安全的起点
- Windows 上更敏感,同样并发数下更容易出现
"WSAENOBUFS",建议降到 20–30
结果聚合时忽略 connection refused 和 no route to host 的语义差异
这两个错误看起来都是“连不上”,但含义完全不同:connection refused 说明目标 IP 在线、端口没服务;no route to host 通常意味着 IP 根本没响应 ARP,大概率离线或不在同一子网。
立即学习“go语言免费学习笔记(深入)”;
- 遇到
connection refused,可以记为“主机存活,端口关闭” - 遇到
no route to host或host unreachable,直接归为“主机不可达”,不用再试其他端口 - Go 里判断得看 error 的底层类型:
if opErr, ok := err.(*net.OpError); ok && opErr.Err != nil,再用strings.Contains(opErr.Err.Error(), "no route to host")匹配
真正难的不是发包,是区分哪些失败是网络路径问题、哪些是服务配置问题、哪些是扫描节奏惹的祸。扫完一个网段后,如果发现某 IP 对所有端口都返回 i/o timeout,先查它是否在本机 ARP 表里 —— 这一步跳过,后面所有分析都可能是错的。










