
本文介绍在 Go 中通过 io.Copy 和接口抽象,将 TCP/Unix 套接字无缝对接 os.Stdin 和 os.Stdout,实现类似 socat 的双向数据流转发,强调类型兼容性与阻塞控制。
本文介绍在 go 中通过 `io.copy` 和接口抽象,将 tcp/unix 套接字无缝对接 `os.stdin` 和 `os.stdout`,实现类似 `socat` 的双向数据流转发,强调类型兼容性与阻塞控制。
Go 语言的标准库设计高度遵循接口契约(如 io.Reader 和 io.Writer),这使得将网络套接字(如 net.Conn)与标准 I/O 流(os.Stdin/os.Stdout)组合使用变得简洁而自然——无需底层文件描述符操作或系统调用重定向。关键在于:*所有 net.Conn 类型均同时实现 io.Reader 和 io.Writer 接口,而 os.Stdin 是 `os.File(满足io.Reader),os.Stdout同样满足io.Writer**。因此,最直接、最符合 Go 惯用法的方式是使用io.Copy` 进行双向流桥接。
以下是一个完整、可运行的示例,实现客户端将本地 Stdin 实时发送至远程 TCP 服务,并将服务响应实时打印到 Stdout:
package main
import (
"io"
"log"
"net"
"os"
)
func main() {
// 连接远程服务(例如 telnet 服务器)
conn, err := net.Dial("tcp", "localhost:8080")
if err != nil {
log.Fatal("无法连接:", err)
}
defer conn.Close()
// 启动两个 goroutine 并发复制:Stdin → Socket,Socket → Stdout
go func() {
_, err := io.Copy(conn, os.Stdin)
if err != io.EOF && err != nil {
log.Printf("Stdin → Socket 复制失败: %v", err)
}
}()
_, err = io.Copy(os.Stdout, conn)
if err != nil && err != io.EOF {
log.Printf("Socket → Stdout 复制失败: %v", err)
}
}⚠️ 注意事项:
- 必须使用 goroutine 分离读写:io.Copy 是阻塞操作,若顺序执行(先 Copy(conn, Stdin) 再 Copy(Stdout, conn)),程序将永远卡在第一步,无法接收响应;
- io.Copy 自动处理 EOF 和错误:当 os.Stdin 关闭(如 Ctrl+D)时,io.Copy 返回 io.EOF,此时写入端自然终止;同理,连接断开会触发读端 io.Copy 结束;
- 无需 bufio 封装:虽然 bufio.Writer 可用于缓冲写入(提升小包性能),但 net.Conn 本身已具备合理缓冲,且 io.Copy 内部已做优化,直接使用更简洁安全;
- 超时与取消控制:生产环境建议结合 context.WithTimeout 和 conn.SetDeadline() 防止永久阻塞;
- Unix 域套接字同样适用:只需将 net.Dial("tcp", ...) 替换为 net.Dial("unix", "/tmp/my.sock"),其余逻辑完全一致。
总结:Go 的接口驱动设计让套接字与标准流的集成极为轻量。核心不是“附加”(attach)而是“组合”(compose)——利用 io.Copy 在满足相同接口的任意 Reader/Writer 之间建立数据通道。这正是 socat 类工具在 Go 中的地道实现方式:清晰、安全、无副作用。


















