Golang pipe在不同场景下远程交互

作者:梦想画家 时间:2024-05-09 09:45:58 

本文介绍Golang pipe,以及在不同场景下的应用。

Pipe介绍

pipe实现从一个进程重定向至另一个进程,它是双向数据通道,用于实现进行间通信。

io.Pipe函数创建内存同步通道,用于连接io.Reader和io.Writer. 本文示例使用环境为:

go version
go version go1.19.3 linux/amd64

Go pipe简单示例

在实现远程交互之前,先看下面简单示例,演示如何使用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)
   }
}

首先创建pipe,然后在协程中给管道的writer写数据,然后使用io.Copy函数从管道Reader中拷贝数据至标准输出:

go func() {
   fmt.Fprint(w, "Hello there\n")
   w.Close()
}()

在协程中写数据是因为每次写PipeWriter都阻塞直到PipeReader完全消费了数据。

运行程序:

go run main.go 
Hello there

通过这个简单示例,展示了管道重定向能力,了解这个基本原理后,下面先看Shell命令的管道,最终我们的目标是通过WEB方式实现远程命令行交互。

Go cmd StdoutPipe

当命令启动时,Cmd的StdoutPipe返回管道连接命令的标准输出:

package main
import (
   "bufio"
   "fmt"
   "log"
   "os"
   "os/exec"
)
func main() {
   cmd := exec.Command("ping", "www.baidu.com")
   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命令,然后从其输出中读取4行. 这行代码启动ping命令:

    cmd := exec.Command("ping", "www.baidu.com")
    stdout, err := cmd.StdoutPipe()
    buf := bufio.NewReader(stdout) 

接着获取命令的标准输出,并保存输出之buf中。下面从缓冲中读取4行:

for {
   line, _, _ := buf.ReadLine()
   if num > 3 {
       os.Exit(0)
   }
   num += 1
   fmt.Println(string(line))
}

读取4行并输出到控制台,运行程序,输出结果如下:

go run main.go
PING www.a.shifen.com (180.101.50.188) 56(84) bytes of data.
64 bytes from 180.101.50.188 (180.101.50.188): icmp_seq=1 ttl=53 time=12.0 ms
64 bytes from 180.101.50.188 (180.101.50.188): icmp_seq=2 ttl=53 time=11.2 ms
64 bytes from 180.101.50.188 (180.101.50.188): icmp_seq=3 ttl=53 time=10.5 ms

通过这个示例,成功地把命令的执行结果捕获到buffer中,并能够增加处理逻辑再输出到控制台。

http请求处理中使用管道

下面示例展示在http请求处理中使用管道。运行date命令,通过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)
}

关键代码为创建管道,并把PipeWriter赋给命令的标准输出和标准错误。

cmd := exec.Command("date")
pr, pw := io.Pipe()
defer pw.Close()
cmd.Stdout = pw
cmd.Stderr = pw

go io.Copy(w, pr)

然后在协程中拷贝PipeReader至http.ResponseWriter.最后运行程序查看结果:

$ go run handler.go 
server started on port 8080

使用curl或浏览器访问地址:localhost:8080,可以看到:

2023年 02月 22日 星期三 17:06:11 CST

修改上面程序,把命令作为参数,即可实现远程交互。下面我们看看如何利用管道给输入端写入数据,包括http请求和命令的标准输入。

利用管道提交post请求json数据

下面示例给https://httpbin.org/post请求地址提交json数据作为请求体。

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))
}

上面示例实现给post请求提交json数据,并读取响应内容。

首先定义管道,然后在协程中给管道Writer写入json数据:

go func() {
   defer w.Close()
   err := json.NewEncoder(w).Encode(&PayLoad{Content: "Hello there!"})
   if err != nil {
       log.Fatal(err)
   }
}()

然后把管道Reader作为参数传入请求:

resp, err := http.Post("https://httpbin.org/post", "application/json", r)

最后读取响应内容:

body, err := ioutil.ReadAll(resp.Body)
if err != nil {
   log.Fatal(err)
}
fmt.Println(string(body))

运行程序输出结果:

go run main.go
{
  "args": {}, 
  "data": "{\"Content\":\"Hello there!\"}\n", 
  "files": {}, 
  "form": {}, 
  "headers": {
    "Accept-Encoding": "gzip", 
    "Content-Type": "application/json", 
    "Host": "httpbin.org", 
    "Transfer-Encoding": "chunked", 
    "User-Agent": "Go-http-client/2.0", 
    "X-Amzn-Trace-Id": "Root=1-63f5c8c6-4a14ee9a2dc14e352f234fae"
  }, 
  // 省略...
}

通过管道读标准输入

下面示例利用管道从标准输入读取数据,并打印数据及数据字节数、块数:

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)
}

首先定义包装标准输入Reader:

r := bufio.NewReader(os.Stdin)
buf := make([]byte, 0, 4*1024)
n, err := r.Read(buf[:cap(buf)])
buf = buf[:n]
nChunks++
nBytes += int64(len(buf))
fmt.Println(string(buf))

然后创建4kb缓冲区,从标准输入读数据至缓冲区。然后计算块数和字节数,最后答应缓冲区内容。

date | go run main.go
2023年 02月 22日 星期三 16:08:17 CST

Bytes: 43 Chunks: 1

这里通过|操作传递date命令的输出结果,显示内容与预期一致。

Go Stat

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:

stat, _ := os.Stdin.Stat()

获取到标准输入的FileInfo结构体后进行判断:

if (stat.Mode() & os.ModeCharDevice) == 0 {

这行判断数据来自管道,反之则为终端。即如果没有管道提供数据,则提示用户输入数据。运行程序:

$ echo "golang" | go run main.go
Hello golang!

$go run main.go
Enter your name: java
Hello java!

来源:https://blog.csdn.net/neweastsun/article/details/129176591

标签:Golang,Go,pipe,远程交互
0
投稿

猜你喜欢

  • php开发微信支付获取用户地址

    2023-09-07 15:12:08
  • ERROR 1222 (21000): The used SELECT statements have a different number of columns

    2024-01-15 02:31:28
  • Python从函数参数类型引出元组实例分析

    2022-12-18 01:51:22
  • 微信小程序之数据缓存的实例详解

    2024-04-19 09:49:53
  • python字典遍历数据的具体做法

    2022-04-19 16:45:33
  • 如何创建一个对索引服务器进行查询的ASP页面?

    2009-11-14 20:54:00
  • 图解Vue 响应式流程及原理

    2024-05-09 15:26:23
  • JS中判断null、undefined与NaN的方法

    2024-04-19 09:54:27
  • Python应用库大全总结

    2023-05-14 01:57:19
  • flask 实现上传图片并缩放作为头像的例子

    2021-09-08 06:32:42
  • Python实现图片裁剪的两种方式(Pillow和OpenCV)

    2022-07-08 12:42:47
  • Python3使用pandas模块读写excel操作示例

    2021-06-30 16:34:47
  • 用python做个代码版的小仙女蹦迪视频

    2022-10-14 12:48:59
  • Python语音识别API实现文字转语音的几种方法

    2023-02-17 01:17:00
  • Python实现微信消息防撤回功能的实例代码

    2023-10-05 14:20:51
  • 介绍讲解MySQL安装下载登录

    2010-10-25 20:34:00
  • PHP面向对象编程之深入理解方法重载与方法覆盖(多态)

    2024-05-22 10:02:25
  • 基于python的opencv图像处理实现对斑马线的检测示例

    2021-07-30 23:02:44
  • 利用django-suit模板添加自定义的菜单、页面及设置访问权限

    2023-01-13 02:09:09
  • 合理的网页设计具有哪些特征

    2007-10-09 13:24:00
  • asp之家 网络编程 m.aspxhome.com