## 介绍
采用管道的通信方式,在Go程序中运行 `子进程` 并实现通信。✉️
按照通用的规则,可以解耦程序,实现插件化。
字节开源的安全项目 `Elkeid` 就是使用的这种方式实现插件化。
https://github.com/bytedance/Elkeid/blob/main/agent/plugin/plugin_linux.go#L43
进程间的通信方式参考下面文章:
[进程间通信IPC (InterProcess Communication)](https://www.jianshu.com/p/c1015f5ffa74)
[进程间的通信方式——pipe(管道)](https://blog.csdn.net/skyroben/article/details/71513385)
[go os/exec 简明教程](https://colobu.com/2020/12/27/go-with-os-exec/)
## 示例
**pstree**
```shell
➜  testProject pstree 29475       
-+= 29475 zhangshun ./server
 \--- 29476 zhangshun /Users/zhangshun/hll/gitlab/testProject/test/pipe/client/plugin
```
**parent**
```golang
func main() {
	workDir := "/Users/zhangshun/hll/gitlab/testProject/test/pipe/client"
	execPath := path.Join(workDir, "plugin")
	cmd := exec.Command(execPath)
	// 创建两个管道,实现全双工
	parentReader, childWriter, err := os.Pipe()
	if err != nil {
		panic(err)
	}
	childReader, parentWriter, err := os.Pipe()
	if err != nil {
		panic(err)
	}
	// 创建子进程的标准错误文件
	errFile, err := os.OpenFile(execPath+".stderr", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0o644)
	defer errFile.Close()
	if err != nil {
		panic(err)
	}
	cmd.Dir = workDir
	cmd.Stderr = errFile
    // 将父进程打开的文件传给子进程
    // 除了标准输入输出0,1,2三个文件外,还可以将父进程的文件传给子进程
	cmd.ExtraFiles = append(cmd.ExtraFiles, childReader, childWriter)
	cmd.Start()
	wg := &sync.WaitGroup{}
	wg.Add(3)
	// 等待子进程结束
	go func() {
		defer wg.Done()
		cmd.Wait()
		parentReader.Close()
		parentWriter.Close()
	}()
	// 读取子进程
	go func() {
		defer wg.Done()
		reader := bufio.NewReader(parentReader)
		buf := make([]byte, 1024)
		for {
			n, err := reader.Read(buf)
			if err != nil {
				fmt.Println(err)
			}
			fmt.Print(string(buf[:n]))
		}
	}()
	// 写入子进程
	go func() {
		defer wg.Done()
		writer := bufio.NewWriterSize(parentWriter, 1024*256)
		for p := 0; p < 10; p++ {
			content := []byte(fmt.Sprintf("parent write data: %d\n", p))
			_, err := writer.Write(content)
			if err != nil {
				fmt.Println(err)
			}
			err = writer.Flush()
			if err != nil {
				fmt.Println(err)
			}
			time.Sleep(time.Second * 1)
		}
	}()
	wg.Wait()
}
```
**child**
```golang
func main() {
    // 实例化父进程传过来的fd
    // 除了标准输入输出0,1,2三个文件外,还可以将父进程的文件传给子进程
	reader := bufio.NewReaderSize(os.NewFile(3, "pipe"), 1024*128)
	writer := bufio.NewWriterSize(os.NewFile(4, "pipe"), 1024*128)
	// 把父进程的输入写到文件中
	logFile, err := os.OpenFile("plugin.log", os.O_CREATE|os.O_APPEND|os.O_RDWR, 0o644)
	defer logFile.Close()
	if err != nil {
		panic(err)
	}
	logWriter := bufio.NewWriterSize(logFile, 2)
	wg := &sync.WaitGroup{}
	wg.Add(2)
	// 读取父进程
	go func() {
		defer wg.Done()
		buf := make([]byte, 1024)
		for {
			n, err := reader.Read(buf)
			if err != nil && err != io.EOF {
				break
			}
			logWriter.Write(buf[:n])
			logWriter.Flush()
		}
	}()
	// 写入父进程
	go func() {
		defer wg.Done()
		for i := 0; i < 10; i++ {
			content := []byte(fmt.Sprintf("child write data: %d\n", i))
			writer.Write(content)
			writer.Flush()
			time.Sleep(time.Second * 1)
		}
	}()
	wg.Wait()
}
```
## cmd 对象
```golang
type Cmd struct {  
    Path         string   // 运行命令的路径,绝对路径或者相对路径  
    Args         []string  // 命令参数  
    Env          []string   // 进程环境,如果环境为空,则使用当前进程的环境  
    Dir          string   // 指定command的工作目录,如果dir为空,则comman在调用进程所在当前目录中运行  
    Stdin        io.Reader // 标准输入,如果stdin是nil的话,进程从null device中读取(os.DevNull),stdin也可以时一个
    						// 文件,否则的话则在运行过程中再开一个goroutine去/读取标准输入  
    Stdout       io.Writer  // 标准输出  
    Stderr       io.Writer // 错误输出,如果这两个(Stdout和Stderr)为空的话,则command运行时将响应的文件描述符连接到
    						// os.DevNull  
    ExtraFiles   []*os.File // 除了标准输入输出0,1,2三个文件外,还可以将父进程的文件传给子进程,打开的文件描述符切片,可为进程添加fd,比如 socket 
    SysProcAttr  *syscall.SysProcAttr // 系统的进程属性
    Process      *os.Process    // Process是底层进程,只启动一次,就是 os.StartProcess 返回的进程对象
    ProcessState *os.ProcessState  // ProcessState包含一个退出进程的信息,当进程调用Wait或者Run时便会产生该信息.  
} 
```
       
          Go 进程通信
 
               
            