开放的编程资料库

当前位置:我爱分享网 > Go教程 > 正文

Golang中使用管道

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教程。

未经允许不得转载:我爱分享网 » Golang中使用管道

感觉很棒!可以赞赏支持我哟~

赞(0) 打赏