用 Go 写个“端口扫描器”,100 行代码扫描你家路由器?(别慌,只是看看谁在开门!)

免责声明:本文仅供学习交流,请勿用于非法扫描他人设备。你扫你自己的电脑,我鼓掌;你扫隔壁老王的服务器,警察叔叔鼓掌 👮‍♂️。

一、故事从一个"好奇"开始

某天深夜,我盯着路由器发呆:"嘿,我家这台小铁盒子,到底开了哪些端口?会不会有神秘服务在偷偷运行?"

于是------我决定写个工具,批量敲门:

"你好,请问 1 号端口在家吗?"

"2 号呢?"

"......65535 号?你睡了吗?"

结果发现:

22 号(SSH)说:"密码不对,走开!" 80 号(HTTP)热情招呼:"欢迎访问我的 404 页面!" 而 1433 号(MSSQL)......根本没人应门 😢 今天,我就带你用 Go 语言,100 行代码实现一个高并发 TCP 端口扫描器,轻松探查目标主机哪些"门"是开着的!

二、核心思路:生产者 + 消费者模型

我们的程序就像一家快递公司:

老板(main):把 65535 个包裹(端口任务)放进仓库(channel)。 快递员(goroutine):100 个(或你指定的数量)工人同时取件、送货(尝试连接)。 成功送达? → 记录日志:"XX 端口有人签收!" 没人收? → 默默扔掉,继续下一件。 整个过程又快又稳,全靠 Go 的并发神器:goroutine + channel!

三、关键 Go 知识点解析

1. goroutine:轻量级线程

go 复制代码
scanWorker() // 启动一个后台任务
  • 不是操作系统线程,而是 Go 调度器管理的"绿色线程"。
  • 启动成本极低,100 个并发轻轻松松。

2. channel:安全通信管道

go 复制代码
taskCh := make(chan string, 1000)
  • 带缓冲的 channel,作为任务队列。
  • 多个 goroutine 安全地从中读取任务,无需加锁!

3. sync.WaitGroup:等待所有任务完成

go 复制代码
wg.Add(1)
defer wg.Done()
wg.Wait()
  • 主 goroutine 会卡在 Wait(),直到所有 worker 执行完毕。
  • 避免程序提前退出。

4. net.DialTimeout:带超时的 TCP 连接

go 复制代码
net.DialTimeout("tcp", "192.168.1.1:80", 1*time.Second)
  • 如果 1 秒内连不上,就放弃,避免卡死。
  • 是端口扫描的核心 API!

5. 命令行参数解析

go 复制代码
ip := os.Args[1]
threads := os.Args[2]
  • 简单直接,适合小型工具。
  • (复杂场景可用 flag 或 cobra 库)

6. 错误处理 & 输入校验

  • 用 net.ParseIP 验证 IP 格式。
  • 用 strconv.Atoi 转换线程数,并检查范围。
  • 用户输错?友好提示,优雅退出。

四、运行效果预览

bash 复制代码
$ ./portscanner 127.0.0.1 50
2025-11-08 22:19:16:330076[main.go:75][INFO]开放的服务:192.168.1.1 [192.168.1.1:25]
2025-11-08 22:19:16:334967[main.go:75][INFO]开放的服务:192.168.1.1 [192.168.1.1:80]
2025-11-08 22:19:17:328821[main.go:75][INFO]开放的服务:192.168.1.1 [192.168.1.1:110]
2025-11-08 22:19:20:333560[main.go:75][INFO]开放的服务:192.168.1.1 [192.168.1.1:443]
...
扫描完成。

同时,日志还会自动保存到 runlog/ 目录,方便后续分析!

五、完整源码奉上!

依赖:github.com/Jjmgx/mgxlog(一个简单的日志库)

安装命令:go get github.com/Jjmgx/mgxlog

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 <IP地址> <线程数>\n", os.Args[0])
		os.Exit(1)
	}

	ip := os.Args[1]
	threadStr := os.Args[2]

	// 验证 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 <= 0 || threads > 10000 {
		fmt.Fprintf(os.Stderr, "错误: 线程数必须是 1~10000 之间的整数\n")
		os.Exit(1)
	}

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

	// 生产者:生成所有端口任务(1~65535)
	go func() {
		defer close(taskCh)
		for port := 1; port <= 65535; port++ {
			taskCh <- 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) // 控制台也输出,看得爽!
}

六、结语:技术无罪,滥用有责

这个小工具展示了 Go 在网络编程和并发处理上的强大能力。你可以在此基础上扩展:

  • 支持端口范围(如 80,443,8000-9000)
  • 识别服务类型(发送 HTTP/TDS/Redis 协议探测包)
  • 输出 JSON / CSV 报告

但请记住:好奇心要有,边界感更要有。只扫描你拥有权限的设备,做一名负责任的"数字侦探"🕵️‍♂️!

往期部分文章列表

相关推荐
用户743835613517 小时前
无锁 Hub:我的 IM 系统为什么用 channel 而不是 mutex 管理在线用户
go
吴佳浩1 天前
Go史上最大“打脸”现场来了:泛型方法终于实现了
后端·go
明月_清风2 天前
深入 Go 并发编程:从 Goroutine 到 Channel 的系统性避坑指南
后端·go
用户34232323763172 天前
开源!Go+Wails+Vue3 手搓一个 PLC 实时监控桌面工具
go
止语Lab2 天前
为什么你的 Go TCP server P99 延迟这么高
go
Andy Dennis3 天前
nsq学习记录
消息队列·go·nsq
韦胖漫谈IT3 天前
选语言不是站队,是选适合问题的工具
java·python·ai·rust·go·技术落地
喵个咪3 天前
GoWind Toolkit Go后端代码生成 完整全流程实战
后端·go·orm
夜悊3 天前
Go网络编程的学习代码示例:客户端/服务端(C/S)模型
go