必须用 net.dialtimeout 而非 net.dial,因系统默认 tcp 超时长达 2–3 分钟,会导致扫描阻塞;需设合理超时(如 500ms)、正确网络类型("tcp")、配对 sync.waitgroup 的 add/done,并用 channel 限流并发。

net.DialTimeout 是 Go 里做 TCP 端口扫描的底线要求,不用它,扫描器基本不可用。
为什么必须用 net.DialTimeout 而不是 net.Dial
系统默认的 TCP 连接超时在 Linux 上通常是 2–3 分钟。扫一个关着的端口卡住两分钟,再多几个并发就全堵死。net.DialTimeout 允许你精确控制尝试时间,比如 500 * time.Millisecond,既避开防火墙对慢速 SYN flood 的检测,又防止 goroutine 僵死。
常见错误:
- 写成
500(单位是纳秒,等于立刻失败) - 超时设成
10 * time.Millisecond:局域网内正常端口会误报为关闭 - 超时设成
2 * time.Second:外网扫描整体拖慢,尤其高并发下误差放大 - 网络类型写成
"tcp4"或带http://前缀——只接受"tcp"或"udp"
sync.WaitGroup 的 Add/Done 必须配对且位置正确
主 goroutine 退出太快,子任务根本没跑完,是初学者最常遇到的“扫描没输出”问题。根本原因不是代码逻辑错,而是同步机制没到位。
关键规则:
-
wg.Add(1)必须在go func()启动前调用,不能放在 goroutine 内部 -
wg.Done()必须在每个 return 路径前执行,推荐用defer wg.Done() - 漏掉任一
Add或Done,wg.Wait()就永远阻塞
并发数不是越多越好,要用 channel 做信号量限流
直接为每个 IP+端口组合起一个 goroutine,轻松上万——结果不是快,而是本机 too many open files、被目标限速、甚至触发云 WAF 的拦截策略。
真正可控的做法是用带缓冲的 channel 当信号量:
-
sem := make(chan struct{}, 20)—— 最多同时发起 20 个连接 - 每个 goroutine 启动前先
sem ,结束时再 <code> - 内网建议 50–100,并发数过高反而因本地调度开销降低吞吐
- 外网建议 5–20,重点是稳,不是猛
如何区分 “open”、“closed”、“filtered” 三种状态
net.DialTimeout 只返回连没连上,但连不上有本质不同的三类原因:明确拒绝(RST)、主机不可达(ICMP)、防火墙静默丢包(无响应)。仅靠 err != nil 判断会严重误报。
实际判断逻辑应分三层:
-
err == nil→ 端口开放,记得立即conn.Close()防 fd 泄漏 -
err != nil && strings.Contains(err.Error(), "connection refused")→ 端口明确关闭 -
err != nil && netErr, ok := err.(net.Error); ok && netErr.Timeout()→ 极大概率被过滤(SYN 无响应)
真正难的不是写出来,是让每种状态都可解释、可复现、不依赖运气。很多看似“扫得快”的脚本,其实只是把 filtered 当 closed 处理了而已。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!











