Go语言实战案例:实现一个并发端口扫描器

本篇为《Go语言100个实战案例 · 网络与并发篇》第8篇,介绍如何使用 Go 实现一个并发端口扫描器。通过 Goroutine 并发扫描多个端口,极大地提升端口扫描的效率。本文不仅讲解了如何使用 Go 的并发特性,还涉及了如何处理超时和错误,保证端口扫描的健壮性和效率。


一、实战背景

端口扫描器是网络安全领域常用的工具,它通过对目标主机的端口进行扫描,检测目标设备是否开放某些服务端口(如 HTTP、FTP、SSH 等)。常见的端口扫描场景包括:

  • 安全审计:检测主机是否存在未授权服务
  • 漏洞扫描:识别可能存在漏洞的服务端口
  • 网络诊断:排查服务器是否正常工作

传统的端口扫描使用串行方式,一个端口一个端口地检查,速度非常慢。而 Go 的并发编程可以极大地提升扫描效率。


二、实战目标

我们将实现一个并发端口扫描器,具备以下功能:

  1. 输入目标主机 IP 和端口范围
  2. 使用并发扫描多个端口
  3. 输出每个端口的开放状态
  4. 使用超时机制控制扫描时间,避免阻塞

三、完整代码实现

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 收集并传递任务结果

这个并发端口扫描器可以帮助你在网络安全和系统维护中快速诊断目标主机的开放端口。通过优化,您还可以将其扩展成更完整的网络扫描工具。


相关推荐
码事漫谈36 分钟前
三步改掉 AI 味,附可直接复制的去 AI 味提示词
后端
IT_陈寒40 分钟前
Redis卡顿的锅,这次真不是大key的错
前端·人工智能·后端
可乐鸡翅yeah_1 小时前
video.js 集成 hls.js 开发 M3U8 播放器,新手高频踩坑
开发语言·前端·javascript·后端·ecmascript·m3u8·音视频在线播放
程序猿_极客1 小时前
【免费】分享一套优质的基于SpringBoot的服装商城管理系统的设计与实现(带可视化图表、协同过滤功能),源码+文档+视频详解(讲解)
java·spring boot·后端·服装商城管理系统
明月_清风2 小时前
面对陌生的 GitHub 项目无从下手?这 4 个网站帮你快速读懂源码
前端·后端·github
bug菌2 小时前
🤔同事突然问我:Spring的注解 @Component 和 @Service 有何不同?
java·spring boot·后端
leobertlan2 小时前
痛苦系列 | DSP-02 频域切片:DTFT与DFT的探索
android·后端
加我攻城狮2 小时前
当大模型把「景区」定位到附近公共厕所:我的 AI 旅行小程序踩坑实录
后端·微信
Lonely丶墨轩2 小时前
用 Vue + Spring Boot + Python 做一个 AI 双人海龟汤游戏:从出题、联机到自动审稿等核心技术设计与实现
spring boot·后端·游戏
猪猪拆迁队3 小时前
跨电脑复制共享-WebRTC 打洞踩坑
前端·后端·go