Go如何实现套接字激活 把TCP连接作为子进程的stdin/stdout传递
问题根因
你直接将*net.TCPConn赋值给exec.Cmd的Stdin/Stdout字段不生效,是因为Go的exec包只有在Stdin/Stdout字段为*os.File类型时,才会直接将对应文件描述符传递给子进程;如果传入的是普通io.Reader/io.Writer类型(比如*net.TCPConn),exec包会内部创建管道中转数据,子进程拿到的stdin/stdout实际是管道的文件描述符,不是原生socket fd,所以调用setsockopt等socket专属系统调用会报错。
实现方案
你只需要将*net.TCPConn转换为*os.File类型再赋值给exec.Cmd即可,核心步骤如下:
- 从accept拿到的
*net.TCPConn中调用File()方法获取对应socket的文件描述符(该方法会自动dup一个新的fd,不受Go runtime对原TCPConn的生命周期管理影响) - 关闭原
*net.TCPConn,避免资源泄漏 - 将得到的
*os.File直接赋值给Cmd的Stdin和Stdout
完整代码示例
package main import ( "net" "os" "os/exec" ) func main() { // 监听指定端口,和你的systemd socket配置一致 listener, err := net.ListenTCP("tcp", &net.TCPAddr{ IP: net.IPv4(0, 0, 0, 0), Port: 10101, }) if err != nil { panic(err) } defer listener.Close() for { // 每来一个连接启动一个子进程处理,对应systemd socket的Accept=yes配置 conn, err := listener.AcceptTCP() if err != nil { continue } go handleConn(conn, "/usr/bin/myprog", []string{"myprog", "-N"}) } } func handleConn(conn *net.TCPConn, cmdPath string, args []string) { // 从TCPConn获取对应的os.File,这里返回的是dup后的新fd connFile, err := conn.File() if err != nil { _ = conn.Close() return } // 关闭原TCPConn,dup出来的fd不受影响 _ = conn.Close() // 子进程退出后关闭我们持有的dup后的fd defer func() { _ = connFile.Close() }() // 构造启动命令 cmd := exec.Cmd{ Path: cmdPath, Args: args, Stdin: connFile, Stdout: connFile, Stderr: os.Stderr, } // 启动并等待子进程退出 _ = cmd.Run() }
相关疑问解释
- C语言中fork和exec的区别不影响fd传递逻辑:你只需要在exec调用前,将目标socket fd dup到0(stdin)和1(stdout)的位置,exec启动的新程序就会直接继承这两个fd,和fork场景逻辑完全一致。
- 你看到的文章复制监听socket是场景差异:那篇文章的逻辑是将监听socket传给子进程,让子进程自己处理accept请求;你的场景是已经在父进程完成accept,只需要把客户端连接的fd传给子进程,所以你要传递的是accept返回的客户端socket fd,不是监听socket fd,你的理解是正确的。
内容的提问来源于stack exchange,提问作者Liam Kelly
相关产品推荐
相关产品推荐

