端口转发核心是用net.Listen监听本地端口、net.Dial拨号目标地址,并通过两个goroutine双向io.Copy实现全双工数据搬运,需设Read/WriteDeadline防阻塞、正确close防泄漏、参数化目标地址、非特权端口避权限问题。
端口转发核心逻辑:用
+
就够了
Go 实现端口转发不靠第三方库,两行核心操作就能跑通:监听本地端口,收到连接后立刻拨到目标地址。关键不是“怎么转发”,而是“怎么不丢包、不断连、不卡死”。
启动监听,注意地址格式必须带
(比如
或
),写成
会 panic 报错
每个
到的
必须开 goroutine 处理,否则第二个连接会阻塞第一个的读写
目标地址不能写死在代码里——用命令行参数或环境变量传入,比如
双向数据流必须显式 copy,别信
一次搞定
很多人以为
转发完一边就完了,结果发现 HTTP 请求能发过去但响应收不到,或者 SSH 连上就断。根本原因是 TCP 是全双工,客户端→代理→服务端 和 服务端→代理→客户端 两条流要**同时、独立、持续**搬运。
必须启动两个 goroutine:一个用
搬数据从远端回本地,另一个用
搬数据从本地到远端
两个
都要加
和
,否则连接泄漏,跑一小时就
别用
或自定义 buffer 大小来“优化”——默认 32KB buffer 已足够,改小反而增加 syscall 次数
超时和粘包无关,但没设
会让代理变“僵尸”
没有超时控制的转发服务,在客户端异常断网、服务端崩溃、中间网络中断时,goroutine 会永远卡在
或
上,连接不释放,内存缓慢上涨。这不是粘包问题,是 socket 状态没管理。
对每个新接受的
,立即调用
,同样对
也设(建议略短,比如 4 分钟)
不要只设
—— 写失败(如对方已关闭)同样会阻塞,
也得设
错误检查必须区分
和超时:
才记日志,否则满屏报错干扰排查
Linux 下绑定特权端口(
)需要额外权限
想把 80 或 443 转发出去?
在普通用户下直接失败,报错
。这不是 Go 的锅,是内核限制。
go语言参考手册 中文CHM版
Go 是一个开源的编程语言,它能让构造简单、可靠且高效的软件变得容易。本文给大家带来Go参考手册,需要的可以来下载! Go是从2007年末由Robert Griesemer, Rob Pike, Ken Thompson主持开发,后来还加入了Ian Lance Taylor, Russ Cox等人,并最终于2009年11月开源,在2012年早些时候发布了Go 1稳定版本。现在Go的开发已经是完全开放的,并且拥有一个活跃的社区。 Go 语言特色 简洁、快速、安全 并行、有趣、开源 内存管理、v数组安全、编译
下载
立即学习
“
go语言免费学习笔记(深入)
”;
最稳妥做法:用非特权端口(如
)运行程序,再用
或
的
提权
避免用
——权限过大,且开发时容易误操作影响系统
Docker 场景下,加
并在容器内用
更安全
真正难的不是写通转发,是让每个连接在各种异常网络条件下都能干净退出。goroutine 是否泄漏、deadline 是否覆盖所有路径、close 是否成对触发——这些细节不打日志根本看不见。
net.Listennet.Dialnet.Listen("tcp", ":8080"):":8080""127.0.0.1:8080""8080""listen tcp: address 8080: missing port in address"Accept()net.Conntarget := flag.String("to", "127.0.0.1:9000", "forward target address")io.Copyio.Copy(dst, src)io.Copy(lConn, rConn)io.Copy(rConn, lConn)io.Copydefer rConn.Close()defer lConn.Close()"too many open files"io.CopyNSetDeadlineReadWritelConnlConn.SetDeadline(time.Now().Add(5 * time.Minute))rConnReadDeadlineWriteDeadlinenet.ErrClosedif err != nil && !errors.Is(err, net.ErrClosed)<1024net.Listen("tcp", ":80")"listen tcp :80: bind: permission denied":8080iptablessystemdCapabilities=CAP_NET_BIND_SERVICEsudo go run main.go--cap-add=NET_BIND_SERVICEsetcap 'cap_net_bind_service=+ep' /app/proxy