今天我们用 OpenCode 从零开发一个命令行端口扫描工具。输入目标 IP 和端口范围,它会并发探测哪些端口处于开放状态,并识别常见服务(如 HTTP、SSH、MySQL)。
正在扫描 127.0.0.1 端口 1-1024... 开放端口: ───────────────────────────── 22 SSH 80 HTTP 443 HTTPS 3306 MySQL ───────────────────────────── 扫描完成:共扫描 1024 个端口,4 个端口开放,耗时 2.34 秒
npm install -g @anthropic-ai/opencode 或参考 opencode.ai)opencode 进入交互会话我对 OpenCode 说:
> 帮我创建一个 Go 项目,用于扫描目标主机的开放端口。要求:支持通过命令行参数指定目标 IP、端口范围、连接超时和并发数,使用 goroutine 并发扫描。
OpenCode 立即创建了项目文件。我补充说明参数细节:
> 参数包括:--host 目标地址,--start 起始端口,--end 结束端口,--timeout 超时秒数,--workers 并发数。main 函数用 flag 包解析参数,创建工作协程池并发连接目标端口。
OpenCode 生成了如下核心代码:
ports := make(chan int, *workers)
results := make(chan int, *workers)
var wg sync.WaitGroup
for i := 0; i < *workers; i++ {
wg.Add(1)
go worker(*host, ports, results, &wg, time.Duration(*timeout)*time.Second)
}
要点:一次对话就把项目结构、参数解析、并发模型全部交代清楚。OpenCode 自动处理了 channel 的创建与关闭,以及 WaitGroup 同步。
扫描出端口号不够直观,我让 OpenCode 加上服务名映射:
> 加上常用端口对应的服务名称识别,比如 22→SSH、80→HTTP、443→HTTPS、3306→MySQL、6379→Redis、5432→PostgreSQL、8080→HTTP-Alt。定义一个 map 映射表,输出结果时显示对应服务名。
OpenCode 理解了需求,补充了 commonPorts 映射表,并将输出格式改为:
service, ok := commonPorts[r.port]
if !ok {
service = "Unknown"
}
fmt.Printf(" %-6d %s\n", r.port, service)
要点:用自然语言描述数据映射关系,OpenCode 能准确翻译成 Go 的 map[int]string 结构。
基础功能跑通后,我希望输出更专业:
> 优化输出效果:扫描开始时打印提示信息;扫描结果按端口号排序输出,用分隔线美化;最后汇总显示总扫描数、开放数、耗时。
OpenCode 添加了 sort.Slice 对结果排序,用 time.Since 统计耗时,并格式化输出了分隔线和汇总信息。完整的主流程如下:
var openPorts []scanResult
startTime := time.Now()
for result := range results {
openPorts = append(openPorts, result)
}
elapsed := time.Since(startTime)
sort.Slice(openPorts, func(i, j int) bool {
return openPorts[i].port < openPorts[j].port
})
fmt.Printf("\n扫描完成:共扫描 %d 个端口,%d 个端口开放,耗时 %.2f 秒\n",
total, len(openPorts), elapsed.Seconds())
要点:优化类需求只需描述期望的最终效果,OpenCode 会自动选择合适的数据结构和算法。
package main
import (
"flag"
"fmt"
"net"
"sort"
"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",
}
type scanResult struct {
port int
}
func main() {
host := flag.String("host", "127.0.0.1", "目标主机地址")
startPort := flag.Int("start", 1, "起始端口")
endPort := flag.Int("end", 1024, "结束端口")
timeout := flag.Int("timeout", 1, "连接超时(秒)")
workers := flag.Int("workers", 100, "并发工作协程数")
flag.Parse()
ports := make(chan int, *workers)
results := make(chan scanResult, *workers)
var wg sync.WaitGroup
for i := 0; i < *workers; i++ {
wg.Add(1)
go worker(*host, ports, results, &wg, time.Duration(*timeout)*time.Second)
}
go func() {
for port := *startPort; port <= *endPort; port++ {
ports <- port
}
close(ports)
}()
go func() {
wg.Wait()
close(results)
}()
var openPorts []scanResult
startTime := time.Now()
fmt.Printf("\n正在扫描 %s 端口 %d-%d...\n\n", *host, *startPort, *endPort)
for result := range results {
openPorts = append(openPorts, result)
}
elapsed := time.Since(startTime)
sort.Slice(openPorts, func(i, j int) bool {
return openPorts[i].port < openPorts[j].port
})
fmt.Println("开放端口:")
fmt.Println("─────────────────────────────")
for _, r := range openPorts {
service, ok := commonPorts[r.port]
if !ok {
service = "Unknown"
}
fmt.Printf(" %-6d %s\n", r.port, service)
}
fmt.Println("─────────────────────────────")
total := *endPort - *startPort + 1
fmt.Printf("\n扫描完成:共扫描 %d 个端口,%d 个端口开放,耗时 %.2f 秒\n",
total, len(openPorts), elapsed.Seconds())
}
func worker(host string, ports <-chan int, results chan<- scanResult, wg *sync.WaitGroup, timeout time.Duration) {
for port := range ports {
address := fmt.Sprintf("%s:%d", host, port)
conn, err := net.DialTimeout("tcp", address, timeout)
if err == nil {
conn.Close()
results <- scanResult{port: port}
}
}
wg.Done()
}
使用方式:
go run main.go --host 127.0.0.1 --start 1 --end 1024 go run main.go --host 192.168.1.1 --start 80 --end 443 --timeout 2
这个端口扫描器从想法到可运行只花了 5 分钟,三句自然语言对话完成。相比手写,省去了查 flag 用法、channel 语法、sync 包 API 的时间——这些细节 OpenCode 全部自动处理。
回顾这次实践,OpenCode 的价值不在于"替你写代码",而在于消除编码过程中的摩擦:你专注描述"要做什么",它负责翻译成"怎么做"。对于小工具开发、原型验证这类场景,效率提升显著。
编译运行试试看,扫一下自己本机的开放端口,你会对结果感到惊讶的。