You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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即可,核心步骤如下:

  1. 从accept拿到的*net.TCPConn中调用File()方法获取对应socket的文件描述符(该方法会自动dup一个新的fd,不受Go runtime对原TCPConn的生命周期管理影响)
  2. 关闭原*net.TCPConn,避免资源泄漏
  3. 将得到的*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()
}

相关疑问解释

  1. C语言中fork和exec的区别不影响fd传递逻辑:你只需要在exec调用前,将目标socket fd dup到0(stdin)和1(stdout)的位置,exec启动的新程序就会直接继承这两个fd,和fork场景逻辑完全一致。
  2. 你看到的文章复制监听socket是场景差异:那篇文章的逻辑是将监听socket传给子进程,让子进程自己处理accept请求;你的场景是已经在父进程完成accept,只需要把客户端连接的fd传给子进程,所以你要传递的是accept返回的客户端socket fd,不是监听socket fd,你的理解是正确的。

内容的提问来源于stack exchange,提问作者Liam Kelly

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.26 13:54:03