今天用 OpenCode 写一个 TCP 端口转发器(Port Forwarder)——把发到本地端口的流量透明地转发到指定的后端地址。这个工具在本地开发中非常实用:调试微服务、代理内网端口、穿透防火墙都可以用它。
tcpfwd -listen :8080 -target 192.168.1.100:3306 就能跑起来opencode 即可进入交互界面)打开终端,进入空项目目录,对 OpenCode 说:
> 我:用 Go 写一个 TCP 端口转发工具 tcpfwd,支持 -listen 和 -target 两个命令行参数。监听本地端口,把每个 TCP 连接双向转发到目标地址。
OpenCode 立刻生成了 main.go,核心逻辑非常清晰——net.Listen 监听端口,net.Dial 连接目标,然后用 io.Copy 双向拷贝数据:
listener, _ := net.Listen("tcp", *listenAddr)
for {
clientConn, _ := listener.Accept()
go func() {
targetConn, _ := net.Dial("tcp", *targetAddr)
go io.Copy(targetConn, clientConn)
io.Copy(clientConn, targetConn)
}()
}
关键点:不需要自己写任何网络 I/O 循环,Go 的 io.Copy 把这件事做完了。OpenCode 比你更懂标准库。
原生代码缺了错误处理和优雅关闭。继续指挥:
> 我:加上错误处理,用 log.Printf 打印连接失败日志。再加上信号处理,收到 Ctrl+C 时先关闭监听器,等待现有连接处理完再退出。
OpenCode 追加了 signal.Notify 和 sync.WaitGroup,改造后的 main 函数:
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigCh
fmt.Println("\n正在关闭...")
listener.Close()
}()
var wg sync.WaitGroup
for {
clientConn, err := listener.Accept()
if err != nil {
select {
case <-sigCh:
wg.Wait()
return
default:
continue
}
}
wg.Add(1)
go func() {
defer wg.Done()
handleConnection(clientConn, *targetAddr)
}()
}
关键点:用 select 区分"主动关闭导致的 Accept 错误"和"真正的网络错误"——这是自己写容易忽略的细节,OpenCode 自动补上了。
> 我:检查代码能否编译,帮我构建成二进制。
$ go build -o tcpfwd .
编译通过,零警告。起一个测试:
# 终端1:启动转发器 $ ./tcpfwd -listen :9999 -target httpbin.org:80 # 终端2:通过转发器发请求 $ curl http://localhost:9999/get
返回正常 JSON,转发器日志打印了连接信息。Ctrl+C 后程序等所有连接处理完毕才退出,优雅。
关键点:整个过程我一行代码都没手写,只是依次描述需求,OpenCode 负责设计、编码、排错。代码量很少,但 goroutine 生命周期管理这种容易写出 bug 的地方,AI 全部处理好了。
package main
import (
"flag"
"fmt"
"io"
"log"
"net"
"os"
"os/signal"
"sync"
"syscall"
)
func main() {
listenAddr := flag.String("listen", ":8080", "监听地址")
targetAddr := flag.String("target", "", "目标地址")
flag.Parse()
if *targetAddr == "" {
fmt.Fprintf(os.Stderr, "用法: tcpfwd -listen :8080 -target 192.168.1.100:3306\n")
os.Exit(1)
}
listener, err := net.Listen("tcp", *listenAddr)
if err != nil {
log.Fatalf("监听失败: %v", err)
}
defer listener.Close()
fmt.Printf("端口转发已启动: %s -> %s\n", *listenAddr, *targetAddr)
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, syscall.SIGINT, syscall.SIGTERM)
go func() {
<-sigCh
fmt.Println("\n正在关闭...")
listener.Close()
}()
var wg sync.WaitGroup
for {
clientConn, err := listener.Accept()
if err != nil {
select {
case <-sigCh:
wg.Wait()
return
default:
log.Printf("接受连接失败: %v", err)
continue
}
}
wg.Add(1)
go func() {
defer wg.Done()
handleConnection(clientConn, *targetAddr)
}()
}
}
func handleConnection(clientConn net.Conn, targetAddr string) {
defer clientConn.Close()
targetConn, err := net.Dial("tcp", targetAddr)
if err != nil {
log.Printf("连接目标失败: %v", err)
return
}
defer targetConn.Close()
var wg sync.WaitGroup
wg.Add(2)
go func() {
defer wg.Done()
io.Copy(targetConn, clientConn)
targetConn.Close()
}()
go func() {
defer wg.Done()
io.Copy(clientConn, targetConn)
clientConn.Close()
}()
wg.Wait()
}
这个工具让我再次确认了用 OpenCode 开发 CLI 工具的高效模式:
先用一句话描述需求,让 AI 生成主干代码
再补充边界条件(错误处理、信号、退出逻辑),AI 自动填充细节
构建验证,中途发现问题直接用自然语言描述,AI 定位并修改
70 行代码,从零到跑通不到 4 分钟。如果是手写,光查 signal.Notify 和 io.Copy 的用法就得翻几分钟文档。OpenCode 的真正价值不是"替你写代码",而是"替你查文档 + 替你写样板代码 + 替你考虑边界情况"——你只需要专注于描述需求。