Go管道教程展示了如何在Golang中使用管道。
管道
管道是一种从一个进程重定向到另一个进程的形式。它是一个单向数据通道,可用于进程间通信。
io.Pipe函数创建一个同步内存管道。它可用于连接需要io.Reader的代码和需要io.Writer的代码。
$ go version go version go1.18.1 linux/amd64
我们使用Go版本1.18。
Go管道简单示例
以下示例演示了io.Pipe函数的用法。
package main
import (
"fmt"
"io"
"log"
"os"
)
func main() {
r, w := io.Pipe()
go func() {
fmt.Fprint(w, "Hello there\n")
w.Close()
}()
_, err := io.Copy(os.Stdout, r)
if err != nil {
log.Fatal(err)
}
}
在代码示例中,我们使用io.Pipe创建了一个管道。我们在goroutine中写入管道的写入器,然后使用io.Copy将数据从管道的读取器复制到标准输出。
go func() {
fmt.Fprint(w, "Hello there\n")
w.Close()
}()
我们在goroutine中将一些数据写入管道的writer。对PipeWriter的每次写入都会阻塞,直到它满足来自PipeReader的一次或多次读取,这些读取完全消耗了写入的数据。
$ go run simple.go Hello there
转到命令标准输出管道
命令的StdoutPipe返回一个管道,该管道将在命令启动时连接到命令的标准输出。
package main
import (
"bufio"
"fmt"
"log"
"os"
"os/exec"
)
func main() {
cmd := exec.Command("ping", "webcode.me")
stdout, err := cmd.StdoutPipe()
if err != nil {
log.Fatal(err)
}
cmd.Start()
buf := bufio.NewReader(stdout)
num := 0
for {
line, _, _ := buf.ReadLine()
if num > 3 {
os.Exit(0)
}
num += 1
fmt.Println(string(line))
}
}
在代码示例中,我们启动了一个ping命令并读取了它的四行输出。
cmd := exec.Command("ping", "webcode.me")
我们创建一个启动ping的命令来测试webcode.me网站的可用性。
stdout, err := cmd.StdoutPipe()
我们得到命令的标准输出。
buf := bufio.NewReader(stdout)
创建了标准输出的阅读器。
for {
line, _, _ := buf.ReadLine()
if num > 3 {
os.Exit(0)
}
num += 1
fmt.Println(string(line))
}
在for循环中,我们读取四行并将它们打印到控制台。
$ go run pingcmd.go PING webcode.me (46.101.248.126) 56(84) bytes of data. 64 bytes from 46.101.248.126 (46.101.248.126): icmp_seq=1 ttl=54 time=29.7 ms 64 bytes from 46.101.248.126 (46.101.248.126): icmp_seq=2 ttl=54 time=35.9 ms 64 bytes from 46.101.248.126 (46.101.248.126): icmp_seq=3 ttl=54 time=37.4 ms
通过管道传输POSTJSON数据
在下面的示例中,我们将JSON数据发布到https://httpbin.org/post。
package main
import (
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
)
type PayLoad struct {
Content string
}
func main() {
r, w := io.Pipe()
go func() {
defer w.Close()
err := json.NewEncoder(w).Encode(&PayLoad{Content: "Hello there!"})
if err != nil {
log.Fatal(err)
}
}()
resp, err := http.Post("https://httpbin.org/post", "application/json", r)
if err != nil {
log.Fatal(err)
}
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
}
该示例向网站发布JSON负载并读取其正文响应。
go func() {
defer w.Close()
err := json.NewEncoder(w).Encode(&PayLoad{Content: "Hello there!"})
if err != nil {
log.Fatal(err)
}
}()
我们在goroutine中将JSON负载写入PipeWriter。
resp, err := http.Post("https://httpbin.org/post", "application/json", r)
http.Post方法需要一个读者作为它的第三个参数;我们在那里传递了一个PipeReader。
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
fmt.Println(string(body))
最后,我们读取响应体并打印它。
$ go run post_json.go
{
"args": {},
"data": "{\"Content\":\"Hello there!\"}\n",
"files": {},
"form": {},
"headers": {
...
通过管道读取标准输入
以下示例创建了一个Go程序,它读取通过管道从标准输入传递过来的数据。
package main
import (
"bufio"
"fmt"
"io"
"log"
"os"
)
func main() {
nBytes, nChunks := int64(0), int64(0)
r := bufio.NewReader(os.Stdin)
buf := make([]byte, 0, 4*1024)
for {
n, err := r.Read(buf[:cap(buf)])
buf = buf[:n]
if n == 0 {
if err == nil {
continue
}
if err == io.EOF {
break
}
log.Fatal(err)
}
nChunks++
nBytes += int64(len(buf))
fmt.Println(string(buf))
if err != nil && err != io.EOF {
log.Fatal(err)
}
}
fmt.Println("Bytes:", nBytes, "Chunks:", nChunks)
}
该示例从标准输入读取数据并打印数据以及读取的字节数和块数。
r := bufio.NewReader(os.Stdin)
我们从标准输入创建一个阅读器。
buf := make([]byte, 0, 4*1024)
这里我们创建一个4KB的缓冲区。
n, err := r.Read(buf[:cap(buf)]) buf = buf[:n]
我们从标准输入读取数据到缓冲区。
nChunks++ nBytes += int64(len(buf))
我们计算读取的块数和字节数。
fmt.Println(string(buf))
我们将缓冲区的内容打印到终端。
$ date | go run read_stdin.go Sun 15 Nov 2020 01:08:13 PM CET Bytes: 32 Chunks: 1
我们将|运算符的输出传递给我们的程序。Go程序读取数据,计算字节数并将数据打印到控制台。
统计
Stat函数返回描述文件的FileInfo结构。我们可以用它来检查终端上是否有来自管道的数据。
package main
import (
"bufio"
"fmt"
"log"
"os"
)
func main() {
stat, _ := os.Stdin.Stat()
if (stat.Mode() & os.ModeCharDevice) == 0 {
var buf []byte
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
buf = append(buf, scanner.Bytes()...)
}
if err := scanner.Err(); err != nil {
log.Fatal(err)
}
fmt.Printf("Hello %s!\n", buf)
} else {
fmt.Print("Enter your name: ")
var name string
fmt.Scanf("%s", &name)
fmt.Printf("Hello %s!\n", name)
}
}
该示例通过管道或提示获取用户的输入。
stat, _ := os.Stdin.Stat()
我们得到了标准输入的FileInfo结构。
if (stat.Mode() & os.ModeCharDevice) == 0 {
我们检查输入数据是否来自管道。
var buf []byte
scanner := bufio.NewScanner(os.Stdin)
for scanner.Scan() {
buf = append(buf, scanner.Bytes()...)
}
我们读取通过终端上的|管道运算符传递的数据。
} else {
fmt.Print("Enter your name: ")
var name string
fmt.Scanf("%s", &name)
fmt.Printf("Hello %s!\n", name)
}
如果管道中没有数据,我们从提示中读取数据。
$ echo "Peter" | go run hello.go Hello Peter! $ go run hello.go Enter your name: Peter Hello Peter!
首先,我们通过管道将数据传递给程序,然后通过提示。
在HTTP处理程序中使用管道
在下面的示例中,我们在HTTP处理程序中使用管道。
package main
import (
"fmt"
"io"
"net/http"
"os/exec"
)
func handler(w http.ResponseWriter, r *http.Request) {
cmd := exec.Command("date")
pr, pw := io.Pipe()
defer pw.Close()
cmd.Stdout = pw
cmd.Stderr = pw
go io.Copy(w, pr)
cmd.Run()
}
func main() {
http.HandleFunc("/", handler)
fmt.Println("server started on port 8080")
http.ListenAndServe(":8080", nil)
}
该示例启动了一个简单的服务器,它返回今天的日期。
cmd := exec.Command("date")
我们将执行日期命令。
pr, pw := io.Pipe() defer pw.Close()
我们创建一个Go管道。
cmd.Stdout = pw cmd.Stderr = pw
我们将PipeWriter传递给命令的标准输出和标准错误输出。
go io.Copy(w, pr)
在协程中,我们将PipeReader的内容复制到http.ResponseWriter。
cmd.Run()
命令通过Run执行。
$ go run handler.go server started on port 8080
我们运行服务器。
$ curl localhost:8080 Sun 15 Nov 2020 02:18:07 PM CET
在不同的终端窗口中,我们使用curl创建一个GET请求。
在本教程中,我们使用了Go中的管道。
列出所有Go教程。
