本篇为《Go语言100个实战案例 · 网络与并发篇》第8篇,介绍如何使用 Go 实现一个并发端口扫描器。通过 Goroutine 并发扫描多个端口,极大地提升端口扫描的效率。本文不仅讲解了如何使用 Go 的并发特性,还涉及了如何处理超时和错误,保证端口扫描的健壮性和效率。
一、实战背景
端口扫描器是网络安全领域常用的工具,它通过对目标主机的端口进行扫描,检测目标设备是否开放某些服务端口(如 HTTP、FTP、SSH 等)。常见的端口扫描场景包括:
- 安全审计:检测主机是否存在未授权服务
- 漏洞扫描:识别可能存在漏洞的服务端口
- 网络诊断:排查服务器是否正常工作
传统的端口扫描使用串行方式,一个端口一个端口地检查,速度非常慢。而 Go 的并发编程可以极大地提升扫描效率。
二、实战目标
我们将实现一个并发端口扫描器,具备以下功能:
- 输入目标主机 IP 和端口范围
- 使用并发扫描多个端口
- 输出每个端口的开放状态
- 使用超时机制控制扫描时间,避免阻塞
三、完整代码实现
1. 扫描器实现(scanner.go)
go
package main
import (
"fmt"
"net"
"strconv"
"sync"
"time"
)
func scanPort(ip string, port int, wg *sync.WaitGroup, resultCh chan<- string) {
defer wg.Done()
address := fmt.Sprintf("%s:%d", ip, port)
conn, err := net.DialTimeout("tcp", address, 1*time.Second) // 设置连接超时时间
if err != nil {
resultCh <- fmt.Sprintf("端口 %d: 关闭", port)
return
}
conn.Close()
resultCh <- fmt.Sprintf("端口 %d: 开放", port)
}
func main() {
var wg sync.WaitGroup
resultCh := make(chan string, 100) // 创建带缓冲的 Channel,缓冲区大小为 100
fmt.Print("请输入目标 IP 地址: ")
var ip string
fmt.Scanln(&ip)
fmt.Print("请输入端口范围(例如:80 100): ")
var startPort, endPort int
fmt.Scanln(&startPort, &endPort)
// 扫描指定范围的端口
for port := startPort; port <= endPort; port++ {
wg.Add(1)
go scanPort(ip, port, &wg, resultCh)
}
// 等待所有扫描任务完成并关闭 Channel
go func() {
wg.Wait()
close(resultCh)
}()
// 打印扫描结果
fmt.Println("\n扫描结果:")
for result := range resultCh {
fmt.Println(result)
}
}
四、运行方式
执行端口扫描器
bash
go run scanner.go
输入示例:
请输入目标 IP 地址: 192.168.1.1
请输入端口范围(例如:80 100): 80 90
输出示例:
yaml
扫描结果:
端口 80: 开放
端口 81: 关闭
端口 82: 关闭
端口 83: 关闭
端口 84: 开放
端口 85: 关闭
端口 86: 开放
端口 87: 关闭
端口 88: 关闭
端口 89: 开放
端口 90: 关闭
五、关键技术点解析
1. 使用 Goroutine 并发扫描端口
每个端口的扫描任务都是由独立的 Goroutine 执行的。这样可以最大化利用 CPU 核心,实现端口扫描的并发化。
go
go scanPort(ip, port, &wg, resultCh)
2. 使用 sync.WaitGroup
等待所有 Goroutine 完成
WaitGroup
用来等待所有扫描任务完成,确保在关闭结果通道之前所有扫描任务已经结束。
go
var wg sync.WaitGroup
wg.Add(1)
3. 使用 net.DialTimeout
设置连接超时
DialTimeout
可以在指定的超时时间内完成连接,如果超时则返回错误。我们在这里设置了 1 秒的超时。
go
conn, err := net.DialTimeout("tcp", address, 1*time.Second)
4. 使用 channel
收集扫描结果
通过 Channel 传递每个端口的扫描结果,避免了主线程和 Goroutine 之间的数据竞争问题。
go
resultCh <- fmt.Sprintf("端口 %d: 开放", port)
5. 缓存扫描结果并打印
使用带缓冲的 Channel resultCh
存储扫描结果。所有任务完成后,我们关闭通道并打印扫描结果。
go
go func() {
wg.Wait()
close(resultCh)
}()
六、优化和扩展方向
优化方向 | 说明 |
---|---|
控制并发数量 | 使用令牌桶算法(Token Bucket)或 sem 控制并发扫描的 Goroutine 数量 |
支持输入多个目标 IP | 扩展支持一次扫描多个 IP 地址 |
扫描端口的协议支持 | 不仅支持 TCP 端口扫描,还可支持 UDP 扫描 |
日志记录 | 将扫描结果输出到日志文件,便于后期分析 |
进度条显示 | 添加扫描进度显示,提升用户体验 |
智能端口扫描 | 根据常见端口列表(如 80、443、21 等)进行扫描 |
七、小结
通过本篇案例,你学会了如何用 Go 实现一个并发端口扫描器,掌握了以下关键技术:
- 使用 Goroutine 实现并发任务处理
- 利用
sync.WaitGroup
等待所有并发任务完成 - 使用
net.DialTimeout
进行超时控制,防止阻塞 - 使用 Channel 收集并传递任务结果
这个并发端口扫描器可以帮助你在网络安全和系统维护中快速诊断目标主机的开放端口。通过优化,您还可以将其扩展成更完整的网络扫描工具。