go语言中的IPC之管道通信

  • 发布时间:2017-02-16 14:22:33
  • 作者:风萧萧兮易水寒
  • 标签:golang

熟悉Unix/C编程的应该对IPC也非常的熟悉,多进程之间的通信主要的手段有管道/信号量/共享内存/Socket等,而管道作为父子进程间进行少量数据传递的有效手段也得到了广泛的应用,在这篇文章中我们来看一下go语言中如何使用管道进行进程进行通信。

管道的使用

在linux下,管道被非常广泛地使用,一般在编程中我们实现了popen等的应用即可提供管道功能。而在命令行中使用地也非常多,|就是最为典型的管道的应用例子。shell会为|符号两侧的命令各创建一个脚本,将左侧的输出管道与右侧的输入管道进行连接,可以进行单向管道通信。 比如我们使用go env来确认go语言的环境变量,然后使用grep从中确认出GOROOT环境变量的值一般会如下这样做

[root@liumiaocn goprj]# go env |grep GOROOT
GOROOT="/usr/local/go"
[root@liumiaocn goprj]#

实现的过程其实go env会启动一个进程, 而grep命令也会产生一个进程,grep的进程会在go env的标准输出中进行检索GOROOT的行的信息然后显示出来,而负责这两个进程间的通信的正是管道。 在c语言中,我们需要父进程中进行fork以及对父进程的基本信息进行处理,同时初期化连接的管道信息从而实现管道通信。接下来,我们来看一下在go语言中是如何实现的

调用操作系统命令

为了方便演示,我们使用标准库os中的api以调用操作系统的命令并在此基础上建立用于通信的管道

例子代码

[root@liumiaocn goprj]# cat basic-ipc-pipe.go
package main

import "fmt"
import "os/exec"

func main() {
        //create cmd
        cmd_go_env := exec.Command("go", "env")
        //cmd_grep:=exec.Command("grep","GOROOT")

        stdout_env, env_error := cmd_go_env.StdoutPipe()
        if env_error != nil {
                fmt.Println("Error happened about standard output pipe ", env_error)
                return
        }

        //env_error := cmd_go_env.Start()
        if env_error := cmd_go_env.Start(); env_error != nil {
                fmt.Println("Error happened in execution ", env_error)
                return
        }

        a1 := make([]byte, 1024)
        n, err := stdout_env.Read(a1)
        if err != nil {
                fmt.Println("Error happened in reading from stdout", err)
        }

        fmt.Printf("Standard output of go env command: %s", a1[:n])
}
[root@liumiaocn goprj]#


执行结果

[root@liumiaocn goprj]# go run basic-ipc-pipe.go
Standard output of go env command: GOARCH="amd64"
GOBIN=""
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="linux"
GOOS="linux"
GOPATH=""
GORACE=""
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/linux_amd64"
CC="gcc"
GOGCCFLAGS="-fPIC -m64 -pthread -fmessage-length=0 -fdebug-prefix-map=/tmp/go-build142715013=/tmp/go-build -gno-record-gcc-switches"
CXX="g++"
CGO_ENABLED="1"
[root@liumiaocn goprj]#


管道连接

通过调用exec.Start启动一个进程,通过StdoutPipe将此调用的输出管道也创建了出来,在这里,我们读取了此输出的信息,确实是go env命令的标准输出,接下来要做的事情就是将此输出的管道与grep命令的进程进行连接了。我们将上面的代码进一步充实:

例子代码

[root@liumiaocn goprj]# cat basic-ipc-pipe.go
package main

import "fmt"
import "os/exec"
import "bufio"
import "bytes"

func main() {
        //create cmd
        cmd_go_env := exec.Command("go", "env")
        cmd_grep := exec.Command("grep", "GOROOT")

        stdout_env, env_error := cmd_go_env.StdoutPipe()
        if env_error != nil {
                fmt.Println("Error happened about standard output pipe ", env_error)
                return
        }

        //env_error := cmd_go_env.Start()
        if env_error := cmd_go_env.Start(); env_error != nil {
                fmt.Println("Error happened in execution ", env_error)
                return
        }
        
        //get the output of go env
        stdout_buf_grep := bufio.NewReader(stdout_env)

        //create input pipe for grep command
        stdin_grep, grep_error := cmd_grep.StdinPipe()
        if grep_error != nil {
                fmt.Println("Error happened about standard input pipe ", grep_error)
                return
        }

        //connect the two pipes together
        stdout_buf_grep.WriteTo(stdin_grep)

        //set buffer for reading
        var buf_result bytes.Buffer
        cmd_grep.Stdout = &buf_result

        //grep_error := cmd_grep.Start()
        if grep_error := cmd_grep.Start(); grep_error != nil {
                fmt.Println("Error happened in execution ", grep_error)
                return
        }

        err := stdin_grep.Close()
        if err != nil {
                fmt.Println("Error happened in closing pipe", err)
                return
        }

        //make sure all the infor in the buffer could be read
        if err := cmd_grep.Wait(); err != nil {
                fmt.Println("Error happened in Wait process")
                return
        }
        fmt.Println(buf_result.String())

}
[root@liumiaocn goprj]#


内容非常简单,无需过多解释,唯一需要注意的就是上一个例子中为了确认中间信息,读出管道的信息,但是此处读完了,输入管道就读不到了,所以注释掉了才能正常执行

执行结果

[root@liumiaocn goprj]# go run basic-ipc-pipe.go
GOROOT="/usr/local/go"

[root@liumiaocn goprj]#


从这里可以看出与go env |grep GOROOT是一样的结果。

总结

本文通过模拟非常简单的管道在go中实现的例子,解释了go语言中匿名管道的使用方式,当然和unix/c一样,go也支持命名管道,通过os.Pipe()即可轻松实现,基本原理均类似,在此不再赘述。