net.dial 超时后仍卡住是因为其无内置超时,依赖系统级tcp超时(约2–3分钟);须显式设置net.dialer.timeout和keepalive以避免阻塞与中间设备断连。

为什么 net.Dial 调用超时后仍卡住?
Go 默认的 net.Dial 没有内置超时控制,直接调用会阻塞直到系统级 TCP 连接超时(Linux 通常 2–3 分钟),根本没法用于端口扫描。必须显式设置 net.Dialer 的 Timeout 和 KeepAlive,否则并发扫几十个端口就卡死。
实操建议:
- 永远不用
net.Dial("tcp", addr),改用&net.Dialer{Timeout: 2 * time.Second, KeepAlive: 30 * time.Second} -
KeepAlive不影响单次连接,但能避免中间 NAT/防火墙过早断连,对长周期扫描更稳定 - 如果目标主机 ICMP 不通但端口开放,
Timeout是唯一能防止挂起的手段
如何安全控制并发数避免被重置或丢包?
并发开太多 goroutine 扫描,本地端口耗尽、SYN 包被丢弃、甚至触发对方防火墙限速,现象是大量 connection refused 或直接无响应。这不是代码 bug,而是网络行为边界问题。
实操建议:
- 用带缓冲的 channel 控制并发,例如
sem := make(chan struct{}, 50),每启一个 goroutine 前sem ,结束后 <code> - 50 是较稳妥起点;内网可提到 200,公网建议 ≤ 20(尤其扫云主机时)
- 别依赖
runtime.GOMAXPROCS调节——它管的是 OS 线程调度,和连接并发量无关
syscall.ECONNREFUSED 和 timeout 怎么区分真实状态?
扫描结果里看到 connection refused,只代表该端口有进程在监听并主动拒绝;而超时(i/o timeout)可能是防火墙 DROP、主机关机、路由不可达,也可能是端口开放但服务不回 SYN-ACK(比如某些 IoT 设备)。不能简单把超时等同于“关闭”。
Colly 是一个用于 Go 语言的快速开源爬取和爬虫框架。它适用于从简单的页面提取到异步爬虫处理大量页面集合,支持请求回调和结构化解析。
实操建议:
- 记录错误类型:用
errors.Is(err, syscall.ECONNREFUSED)判断明确拒绝;用strings.Contains(err.Error(), "timeout")捕获超时 - 对超时端口,可加一次重试(间隔 500ms),排除瞬时丢包;但别盲目重试多次,容易暴露扫描行为
- 若同一 IP 多个端口都超时,优先怀疑网络层问题(如 ICMP 被禁、ACL 拦截),而非逐个端口再试
怎么让扫描结果不漏掉快速关闭的连接?
有些服务(如 Nginx 默认配置)收到 SYN 后立即发 RST 关闭,net.Conn 建立后立刻 Close(),但 Go 的 Write 或 Read 可能还没来得及执行就结束了,导致你以为“连上了”,其实什么都没拿到。
实操建议:
- 不要只靠
err == nil判定端口开放;至少尝试写一个字节(如conn.Write([]byte{'\n'}))并忽略返回值,触发底层确认 - 更稳妥的做法是:连接成功后,启动一个
time.AfterFunc(100 * time.Millisecond, conn.Close),防止 goroutine 泄漏 - 如果要识别服务 Banner,必须设
SetReadDeadline,否则Read可能永久阻塞
真正难的不是写通逻辑,而是理解每个 err 背后对应的网络动作——RST、ICMP unreachable、SYN-DROP、TCP retransmit timeout,它们在 Go 错误里长得差不多,但含义天差地别。
golang免费学习笔记(深入):立即使用
在学习笔记中,你将探索golang的核心概念和高级技巧!










