今日目标:理论学得再多,不如写个工具操练一下。今天我们将利用 Go 的并发特性,编写一个多线程端口扫描器。你会亲眼看到它比 Python 单线程版本快 100 倍以上,并在此过程中掌握 “Worker Pool” (工作池) 这一工业级并发模式。
学习内容 (30 mins)
在开始写代码前,先搞懂我们要解决的核心问题和设计思路。核心原理:net.DialTimeout (15 mins)
核心原理:net.DialTimeout (15 mins)
如何判断一个端口是否开放?
- 探测逻辑: 我们使用
net.DialTimeout("tcp", "host:port", timeout)尝试建立 TCP 连接。 - 结果判断:
- 如果不报错 (
err == nil):说明三次握手成功,端口是通的。 - 如果报错(超时或拒绝):说明端口关闭或被防火墙拦截。
- 如果不报错 (
- 为什么一定要 Timeout: 默认的 TCP 连接超时时间可能长达几分钟。如果不设置超时,扫描一个不可达的 IP 会把你卡死很久。
并发架构:Worker Pool (工作池) (15 mins)
并发架构:Worker Pool (工作池) (15 mins)
为什么不直接启动 1024 个协程?虽然 Goroutine 很轻,但如果直接瞬间并发 1024 个网络连接:
- 可能会触发操作系统的 “Too many open files” 限制(文件句柄耗尽)。
- 可能会被防火墙判定为 DDoS 攻击 瞬间封锁你的 IP。
- Job Queue: 创建一个
jobs通道,里面放着要扫描的端口号(如 1-1024)。 - Workers: 启动固定数量(如 100 个)的 Worker 协程。
- 流程: 每个 Worker 抢到一个端口 -> 扫完 -> 再抢下一个。这样并发数永远稳定在 100。
代码任务 (90 mins)
我们将通过两个步骤,从基础函数到完整并发工具。核心:基础连通性检查函数
这是所有网络工具的基石。先写一个独立的函数,专注于检测单个端口。代码解释:
net.DialTimeout("tcp", address, timeout):尝试建立 TCP 连接,带超时err == nil:连接成功,端口开放conn.Close():必须关闭连接,防止资源泄漏- 超时设置:1 秒超时,防止长时间等待
- 总是设置超时:网络操作可能永远不返回,必须设置超时
- 及时释放资源:连接、文件等资源必须及时关闭
- 错误处理:网络操作可能失败,必须处理所有错误情况
main 调用一下 CheckPort("scanme.nmap.org", 80),应该返回 true。验证步骤:- 测试开放端口:
CheckPort("scanme.nmap.org", 80)应该返回true - 测试关闭端口:
CheckPort("scanme.nmap.org", 9999)应该返回false - 测试超时:尝试连接一个不存在的 IP,观察超时行为
实战:完整的并发扫描器
使用 Worker Pool 机制实现多线程扫描。这个代码可以直接作为面试作品。代码解释:
- Worker Pool 模式:固定数量的 Worker,通过 Channel 分发任务
make(chan int, 100):带缓冲的通道,可以暂存 100 个任务for port := range jobs:Worker 从通道中取任务,直到通道关闭close(jobs):关闭通道,通知 Worker 没有更多任务
- 控制并发数:避免创建过多 Goroutine,防止资源耗尽
- 任务队列:通过 Channel 实现任务队列,解耦生产者和消费者
- 优雅关闭:通过关闭 Channel 通知 Worker 停止工作
- 运行程序:
go run 25_scanner.go - 你应该能看到程序在几秒钟内完成扫描,并列出
80,22等开放端口 - 观察 Worker 的并发执行,理解 Worker Pool 的工作方式
- 尝试修改 Worker 数量,观察性能变化
拓展任务 (30 mins)
命令行参数化
任务:使用
flag 包,让用户可以指定 IP 和并发数。提示:host := flag.String("host", "127.0.0.1", "Target IP")threads := flag.Int("t", 50, "Number of threads")- 运行:
./scanner -host 8.8.8.8 -t 100
更详细的探测
任务:只知道端口开放还不够。挑战:对于开放的端口,尝试读取它返回的前 1KB 数据(Banner Grabbing),看看能不能识别出它是 SSH 还是 HTTP 服务器。
今日产出物
~/projects/learn-go/25_scanner.go- 高性能端口扫描器
参考代码
查看参考代码
在 GitHub 查看完整的示例代码
在线运行
使用 Replit 运行 (网络可能受限)
实际应用场景
性能大比拼
- Python (单线程): 扫描 1000 个端口约需 5 分钟 (300秒)。
- Go (50 并发 Worker): 扫描 1000 个端口约需 3 秒。
- 这就是 100 倍 的性能差距,也是为什么 Go 是开发网络工具的首选。
安全合规提示
- 不要扫公网 IP: 除非是你自己的服务器。扫描他人 IP 在很多国家是违法的。
- 内网巡检: 这个工具非常适合用于企业内网巡检,快速发现有哪些机器违规开放了高危端口(如 3389, 23)。
回到目录
查看完整进度
下一天: 工具开发 II
Day 26 | Go 实战工具开发 (二)