必须用 net.dialtimeout 而非 net.dial,因后者无超时易卡住数十秒,而端口扫描需快速试探;推荐复用 net.dialer 并设 timeout,超时值须带单位(如 500time.millisecond),并发需用 chan struct{} 限流防自爆。

为什么必须用 net.DialTimeout,而不是 net.Dial
因为 net.Dial 默认不设超时,TCP 握手失败时会卡住几十秒(Linux 下常为 2–3 分钟),扫一个死端口就拖垮整个流程;而端口扫描本质是“快速试探”,不是等连接建好再干活。
-
net.DialTimeout("tcp", addr, 500*time.Millisecond)能强制在半秒内返回,让 goroutine 不僵死 - Go 1.19+ 更推荐复用
*net.Dialer并设Timeout字段,避免反复 new 实例带来的内存抖动 - 别写
500这种裸数字——单位是纳秒,实际等于立刻失败;必须写成500 * time.Millisecond - 局域网建议 500ms–1s,公网或高延迟链路可放宽到 2s;低于 200ms 容易漏掉嵌入式设备或负载高的虚拟机
并发控制:用 chan struct{} 做令牌桶,别靠 sync.WaitGroup 硬扛
开几千个 goroutine 同时拨号,不是快,是自爆:本地端口耗尽、connect: cannot assign requested address、被防火墙限速甚至拉黑。
- 声明
sem := make(chan struct{}, 100),缓冲大小从 50 起调,观察ss -s中 “TCP: inuse” 是否稳定 - 每个 goroutine 开始前写
sem ,结束前 <code>,这才是安全的计数器 -
sync.WaitGroup只负责“等所有任务结束”,不能替代限流;混用时务必先wg.Add(1)再发 token,别反了 - Windows 下高并发可能触发
WSAENOBUFS,这时要降并发 + 检查net.ipv4.ip_local_port_range
怎么判断错误类型:别用 err.Error() 字符串匹配
“timeout” 和 “connection refused” 看似简单,但底层错误是嵌套结构:*net.OpError 包着 *os.SyscallError,直接字符串匹配极易漏判或误判。
使用 uber-go/dig 在 Golang 中实现依赖注入——基于反射容器,提供 Provide/Invoke、dig.In/dig.Out 参数与结果对象,支持命名依赖等
- 用类型断言:
if opErr, ok := err.(*net.OpError); ok && opErr.Err != nil - 判断是否超时:优先用
opErr.Timeout(),它统一覆盖所有超时场景,比查字符串靠谱 - 判断是否明确拒绝:
errors.Is(opErr.Err, syscall.ECONNREFUSED)(跨平台)或opErr.Err.Error() == "connection refused"(Linux/macOS) - 没回包 ≠ 关闭,很可能是防火墙
DROP,应标记为 “filtered”,而非 “closed”
扫 localhost 时,地址写法直接影响结果
“localhost” 不是魔法字符串,它走 DNS 解析,可能被 /etc/hosts 干扰,也可能解析成 IPv6 地址,而目标服务只监听 IPv4 回环。
- 测试本机服务,优先用
"127.0.0.1:22"或"[::1]:22",明确协议栈 - Docker 容器内 SSH 常只绑定
127.0.0.1,不响应localhost或[::1] - 避免在代码里拼接
"localhost:" + port,除非你确认服务监听了所有解析结果 - 不确定时,先用
ss -tln | grep :PORT看服务到底 bind 在哪个地址上
端口扫描看着简单,真正稳住得抠三件事:超时值设对、并发令牌拿稳、错误类型断准。漏掉任何一点,扫出来的结果就只是“看起来像”,不是“能信的”。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










