月下无赖 發表於 2025-11-18 09:43:54

深入详解如何使用Go实现端口扫描器

<div id="navCategory"><h5 class="catalogue">目录</h5><ul class="first_class_ul"><li><a href="#_label0">一、故事从一个&quot;好奇&quot;开始</a></li><li><a href="#_label1">二、核心思路:生产者 + 消费者模型</a></li><li><a href="#_label2">三、关键 Go 知识点解析</a></li><ul class="second_class_ul"><li><a href="#_lab2_2_0">1. goroutine:轻量级线程</a></li><li><a href="#_lab2_2_1">2. channel:安全通信管道</a></li><li><a href="#_lab2_2_2">3. sync.WaitGroup:等待所有任务完成</a></li><li><a href="#_lab2_2_3">4. net.DialTimeout:带超时的 TCP 连接</a></li><li><a href="#_lab2_2_4">5. 命令行参数解析</a></li><li><a href="#_lab2_2_5">6. 错误处理 &amp; 输入校验</a></li></ul><li><a href="#_label3">四、运行效果预览</a></li><ul class="second_class_ul"></ul><li><a href="#_label4">五、完整源码奉上!</a></li><ul class="second_class_ul"></ul><li><a href="#_label5">六、结语:技术无罪,滥用有责</a></li><ul class="second_class_ul"></ul></ul></div><p>免责声明:本文仅供学习交流,请勿用于非 法扫描他人设备。</p>
<p class="maodian"><a name="_label0"></a></p><h2>一、故事从一个&quot;好奇&quot;开始</h2>
<p>某天深夜,我盯着路由器发呆:&quot;嘿,我家这台小铁盒子,到底开了哪些端口?会不会有神秘服务在偷偷运行?&quot;</p>
<p>于是&mdash;&mdash;我决定写个工具,批量敲门:</p>
<p>&quot;你好,请问 1 号端口在家吗?&quot;</p>
<p>&quot;2 号呢?&quot;</p>
<p>&quot;&hellip;&hellip;65535 号?你睡了吗?&quot;</p>
<p>结果发现:</p>
<p>22 号(SSH)说:&quot;密码不对,走开!&quot; 80 号(HTTP)热情招呼:&quot;欢迎访问我的 404 页面!&quot; 而 1433 号(MSSQL)&hellip;&hellip;根本没人应门 😢 今天,我就带你用 Go 语言,100 行代码实现一个高并发 TCP 端口扫描器,轻松探查目标主机哪些&quot;门&quot;是开着的!</p>
<p class="maodian"><a name="_label1"></a></p><h2>二、核心思路:生产者 + 消费者模型</h2>
<p>我们的程序就像一家快递公司:</p>
<p>老板(main):把 65535 个包裹(端口任务)放进仓库(channel)。 快递员(goroutine):100 个(或你指定的数量)工人同时取件、送货(尝试连接)。 成功送达? &rarr; 记录日志:&quot;XX 端口有人签收!&quot; 没人收? &rarr; 默默扔掉,继续下一件。 整个过程又快又稳,全靠 Go 的并发神器:goroutine + channel!</p>
<p class="maodian"><a name="_label2"></a></p><h2>三、关键 Go 知识点解析</h2>
<p class="maodian"><a name="_lab2_2_0"></a></p><h3>1. goroutine:轻量级线程</h3>
<div class="jb51code"><pre class="brush:go;">scanWorker() // 启动一个后台任务
</pre></div>
<ul><li>不是操作系统线程,而是 Go 调度器管理的&quot;绿色线程&quot;。</li><li>启动成本极低,100 个并发轻轻松松。</li></ul>
<p class="maodian"><a name="_lab2_2_1"></a></p><h3>2. channel:安全通信管道</h3>
<div class="jb51code"><pre class="brush:go;">taskCh := make(chan string, 1000)
</pre></div>
<ul><li>带缓冲的 channel,作为任务队列。</li><li>多个 goroutine 安全地从中读取任务,无需加锁!</li></ul>
<p class="maodian"><a name="_lab2_2_2"></a></p><h3>3. sync.WaitGroup:等待所有任务完成</h3>
<div class="jb51code"><pre class="brush:go;">wg.Add(1)
defer wg.Done()
wg.Wait()
</pre></div>
<ul><li>主 goroutine 会卡在 Wait(),直到所有 worker 执行完毕。</li><li>避免程序提前退出。</li></ul>
<p class="maodian"><a name="_lab2_2_3"></a></p><h3>4. net.DialTimeout:带超时的 TCP 连接</h3>
<div class="jb51code"><pre class="brush:go;">net.DialTimeout("tcp", "192.168.1.1:80", 1*time.Second)
</pre></div>
<ul><li>如果 1 秒内连不上,就放弃,避免卡死。</li><li>是端口扫描的核心 API!</li></ul>
<p class="maodian"><a name="_lab2_2_4"></a></p><h3>5. 命令行参数解析</h3>
<div class="jb51code"><pre class="brush:go;">ip := os.Args
threads := os.Args
</pre></div>
<ul><li>简单直接,适合小型工具。</li><li>(复杂场景可用 flag 或 cobra 库)</li></ul>
<p class="maodian"><a name="_lab2_2_5"></a></p><h3>6. 错误处理 &amp; 输入校验</h3>
<ul><li>用 net.ParseIP 验证 IP 格式。</li><li>用 strconv.Atoi 转换线程数,并检查范围。</li><li>用户输错?友好提示,优雅退出。</li></ul>
<p class="maodian"><a name="_label3"></a></p><h2>四、运行效果预览</h2>
<blockquote><p>$ ./portscanner 127.0.0.1 50<br />2025-11-08 22:19:16:330076开放的服务:192.168.1.1 <br />2025-11-08 22:19:16:334967开放的服务:192.168.1.1 <br />2025-11-08 22:19:17:328821开放的服务:192.168.1.1 <br />2025-11-08 22:19:20:333560开放的服务:192.168.1.1 <br />...<br />扫描完成。</p></blockquote>
<p>同时,日志还会自动保存到 runlog/ 目录,方便后续分析!</p>
<p class="maodian"><a name="_label4"></a></p><h2>五、完整源码奉上!</h2>
<p>依赖:github.com/Jjmgx/mgxlog(一个简单的日志库)</p>
<p>安装命令:<code>go get github.com/Jjmgx/mgxlog</code></p>
<div class="jb51code"><pre class="brush:go;">package main

import (
        "fmt"
        "net"
        "os"
        "strconv"
        "sync"
        "time"

        "github.com/Jjmgx/mgxlog"
)

var wg sync.WaitGroup
var taskCh = make(chan string, 1000) // 任务通道:ip:port

var logger, _ = mgxlog.NewMgxLog("runlog/", 10*1024*1024, 100, 3, 1000)

func main() {
        if len(os.Args) != 3 {
                fmt.Fprintf(os.Stderr, "用法: %s &lt;IP地址&gt; &lt;线程数&gt;\n", os.Args)
                os.Exit(1)
        }

        ip := os.Args
        threadStr := os.Args

        // 验证 IP 格式
        if net.ParseIP(ip) == nil {
                fmt.Fprintf(os.Stderr, "错误: 无效的 IP 地址 '%s'\n", ip)
                os.Exit(1)
        }

        // 解析线程数
        threads, err := strconv.Atoi(threadStr)
        if err != nil || threads &lt;= 0 || threads &gt; 10000 {
                fmt.Fprintf(os.Stderr, "错误: 线程数必须是 1~10000 之间的整数\n")
                os.Exit(1)
        }

        // 启动 worker goroutines
        for i := 0; i &lt; threads; i++ {
                wg.Add(1)
                go scanWorker()
        }

        // 生产者:生成所有端口任务(1~65535)
        go func() {
                defer close(taskCh)
                for port := 1; port &lt;= 65535; port++ {
                        taskCh &lt;- fmt.Sprintf("%s:%d", ip, port)
                }
        }()

        wg.Wait()
        fmt.Println("扫描完成。")
}

func scanWorker() {
        defer wg.Done()
        for ipPort := range taskCh {
                scanPort(ipPort)
        }
}

func scanPort(ipPort string) {
        conn, err := net.DialTimeout("tcp", ipPort, 1*time.Second)
        if err != nil {
                return // 连不上?下一个!
        }
        defer conn.Close()

        addr := conn.RemoteAddr().(*net.TCPAddr)
        msg := fmt.Sprintf("开放的服务:%s [%s:%d]", addr.IP, addr.IP, addr.Port)
        logger.Info(msg)
        fmt.Println(msg) // 控制台也输出,看得爽!
}
</pre></div>
<p class="maodian"><a name="_label5"></a></p><h2>六、结语:技术无罪,滥用有责</h2>
<p>这个小工具展示了 Go 在网络编程和并发处理上的强大能力。你可以在此基础上扩展:</p>
<ul><li>支持端口范围(如 80,443,8000-9000)</li><li>识别服务类型(发送 HTTP/TDS/Redis 协议探测包)</li><li>输出 JSON / CSV 报告</li></ul>
<p>但请记住:好奇心要有,边界感更要有。只扫描你拥有权限的设备,做一名负责任的&quot;数字侦探&quot;!</p>
頁: [1]
查看完整版本: 深入详解如何使用Go实现端口扫描器