今天我们用 OpenCode 从零开发一个 Go 语言命令行端口扫描器。它能快速检测目标主机的 TCP 端口开放状态,支持并发扫描、常见服务识别和实时进度显示。最终代码约 100 行,演示环境为 Windows + Go 1.21 + OpenCode。
安装 Go(1.21+)
安装 OpenCode:参考 opencode.ai 文档
终端进入项目目录,执行 opencode 启动交互会话
我对 OpenCode 说:
> 帮我创建一个 Go 项目 portscan,功能是 TCP 端口扫描器。接收命令行参数:目标主机、起始端口、结束端口。先用 net.DialTimeout 实现单线程扫描,超时 1 秒。
OpenCode 自动完成了三件事:创建 main.go、执行 go mod init、执行 go build 验证编译。生成的初始代码:
package main
import (
"fmt"
"net"
"os"
"strconv"
"time"
)
func main() {
if len(os.Args) < 3 {
fmt.Println("用法: portscan <host> <start-port> [end-port]")
os.Exit(1)
}
host := os.Args[1]
startPort, _ := strconv.Atoi(os.Args[2])
endPort := startPort
if len(os.Args) >= 4 {
endPort, _ = strconv.Atoi(os.Args[3])
}
for port := startPort; port <= endPort; port++ {
address := fmt.Sprintf("%s:%d", host, port)
conn, err := net.DialTimeout("tcp", address, 1*time.Second)
if err == nil {
fmt.Printf("端口 %d 开放\n", port)
conn.Close()
}
}
}
要点:OpenCode 不仅写代码,还会主动初始化项目结构、验证编译。你只描述要什么,不必关心 Go 的项目脚手架细节。
单线程扫描 1000 个端口需要约 16 分钟,完全不可接受。我对 OpenCode 说:
> 改用 goroutine 并发扫描,用带缓冲的 channel 做并发控制限制 300 并发。扫描完成后对结果排序输出,并加耗时统计。
OpenCode 直接重构了 main 函数,引入 sync.WaitGroup、sync.Mutex 和 channel 信号量模式:
var wg sync.WaitGroup
var mu sync.Mutex
var openPorts []int
sem := make(chan struct{}, 300)
start := time.Now()
for port := startPort; port <= endPort; port++ {
wg.Add(1)
sem <- struct{}{}
go func(p int) {
defer wg.Done()
defer func() { <-sem }()
if scanPort(host, p) {
mu.Lock()
openPorts = append(openPorts, p)
mu.Unlock()
}
}(port)
}
wg.Wait()
sort.Ints(openPorts)
fmt.Printf("扫描完成,发现 %d 个开放端口,耗时 %v\n", len(openPorts), time.Since(start))
要点:我只说了"并发扫描",OpenCode 自动处理了并发上限控制、线程安全和结果排序——这些都是工程中容易踩坑的地方。
基本功能跑通了,但用户体验还可以更好。我对 OpenCode 说:
> 加两个功能:1)识别常见端口对应的服务名称(80→HTTP,443→HTTPS 等);2)显示实时扫描进度(已扫数/总数 百分比)。
OpenCode 在原有代码上增量追加:
commonPorts 映射表,覆盖 20 个常见端口服务sync.Mutex 保护下的扫描计数,用 time.Ticker 实现进度刷新进度条核心实现:
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-done:
goto finish
case <-ticker.C:
fmt.Printf("\r进度: %d/%d (%.1f%%) 耗时: %v",
scanned, totalPorts,
float64(scanned)/float64(totalPorts)*100,
time.Since(start))
}
}
finish:
要点:两次增量修改都在不破坏现有逻辑的前提下完成。OpenCode 理解上下文,精准定位插入点。
我对 OpenCode 说:
> 用 scanme.nmap.org 测试扫描 1-1024 端口,验证输出。
OpenCode 执行 go run main.go scanme.nmap.org 1 1024,输出:
目标: scanme.nmap.org 端口范围: 1-1024 (1024 个端口) ✓ 端口 22 开放 SSH ✓ 端口 25 开放 SMTP ✓ 端口 80 开放 HTTP ✓ 端口 443 开放 HTTPS ✓ 端口 9929 开放 unknown 扫描完成!共发现 5 个开放端口,耗时 5.23s
1024 个端口不到 6 秒扫完,并发优势一目了然。
package main
import (
"fmt"
"net"
"os"
"sort"
"strconv"
"sync"
"time"
)
var commonPorts = map[int]string{
21: "FTP", 22: "SSH", 23: "Telnet", 25: "SMTP",
53: "DNS", 80: "HTTP", 110: "POP3", 143: "IMAP",
443: "HTTPS", 993: "IMAPS", 995: "POP3S",
3306: "MySQL", 3389: "RDP", 5432: "PostgreSQL",
6379: "Redis", 8080: "HTTP-Alt", 8443: "HTTPS-Alt",
27017: "MongoDB", 9092: "Kafka",
}
func main() {
if len(os.Args) < 3 {
fmt.Println("portscan - 简单快速的 TCP 端口扫描器")
fmt.Println()
fmt.Println("用法: portscan <host> <start-port> [end-port]")
fmt.Println()
fmt.Println("示例:")
fmt.Println(" portscan scanme.nmap.org 80 # 扫描单个端口")
fmt.Println(" portscan scanme.nmap.org 1 1024 # 扫描端口范围")
fmt.Println(" portscan 192.168.1.1 1 65535 # 扫描全部端口")
os.Exit(1)
}
host := os.Args[1]
startPort, err := strconv.Atoi(os.Args[2])
if err != nil || startPort < 1 || startPort > 65535 {
fmt.Println("错误: 无效的起始端口号")
os.Exit(1)
}
endPort := startPort
if len(os.Args) >= 4 {
endPort, err = strconv.Atoi(os.Args[3])
if err != nil || endPort < startPort || endPort > 65535 {
fmt.Println("错误: 无效的结束端口号")
os.Exit(1)
}
}
totalPorts := endPort - startPort + 1
fmt.Printf("目标: %s 端口范围: %d-%d (%d 个端口)\n\n",
host, startPort, endPort, totalPorts)
var wg sync.WaitGroup
var mu sync.Mutex
var openPorts []int
var scanned int
sem := make(chan struct{}, 300)
start := time.Now()
for port := startPort; port <= endPort; port++ {
wg.Add(1)
sem <- struct{}{}
go func(p int) {
defer wg.Done()
defer func() { <-sem }()
if service := scanPort(host, p); service != "" {
mu.Lock()
openPorts = append(openPorts, p)
if name, ok := commonPorts[p]; ok {
fmt.Printf(" ✓ 端口 %-5d 开放 %s\n", p, name)
} else {
fmt.Printf(" ✓ 端口 %-5d 开放 %s\n", p, service)
}
mu.Unlock()
}
mu.Lock()
scanned++
mu.Unlock()
}(port)
}
done := make(chan struct{})
go func() {
wg.Wait()
close(done)
}()
ticker := time.NewTicker(500 * time.Millisecond)
defer ticker.Stop()
for {
select {
case <-done:
goto finish
case <-ticker.C:
mu.Lock()
s := scanned
mu.Unlock()
fmt.Printf("\r进度: %d/%d (%.1f%%) 耗时: %v",
s, totalPorts,
float64(s)/float64(totalPorts)*100,
time.Since(start))
}
}
finish:
sort.Ints(openPorts)
fmt.Printf("\r扫描完成!共发现 %d 个开放端口,耗时 %v\n",
len(openPorts), time.Since(start))
}
func scanPort(host string, port int) string {
address := fmt.Sprintf("%s:%d", host, port)
conn, err := net.DialTimeout("tcp", address, 800*time.Millisecond)
if err != nil {
return ""
}
defer conn.Close()
if name, ok := commonPorts[port]; ok {
return name
}
return "unknown"
}
整个开发过程,我只用 4 句话就完成了从创建到测试的全部工作:
"帮我创建一个 Go 端口扫描器…" → 生成骨架 + 初始化项目
"改用 goroutine 并发…" → 并发重构 + 线程安全
"加服务识别和进度条…" → 增量功能追加
"用 scanme.nmap.org 测试…" → 编译运行验证
全程无需查文档、不用手写一行代码。这就是 OpenCode 的核心价值——你负责定义"做什么",AI 负责解决"怎么做"。项目虽小但覆盖了 Go 并发编程、网络通信、CLI 交互三大实战领域,完全可以作为学习 goroutine + channel 的入门案例。
运行方式:go run main.go <host> <start-port> [end-port],快去试试吧。