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

目录
  • Pipe介绍
  • Go pipe简单示例
  • Go cmd StdoutPipe
  • http请求处理中使用管道
  • 利用管道提交post请求json数据
  • 通过管道读标准输入
  • Go Stat
  • 总结

本文介绍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!

总结

本文介绍了Golang管道的使用,除了实现远程命令交互,还介绍了获取标准输入内容、判断标准输入数据来源。读者组合这些简单示例,一定能够编写出炫酷的应用。

到此这篇关于Golang pipe在不同场景下远程交互的文章就介绍到这了,更多相关Golang pipe远程交互内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

(0)

相关推荐

  • go语言使用pipe读取子进程标准输出的方法

    本文实例讲述了go语言使用pipe读取子进程标准输出的方法.分享给大家供大家参考.具体如下: 其核心代码如下: 复制代码 代码如下: cmd := exec.Command("cmd", "args") stdout, err := cmd.StdoutPipe() cmd.Start() r := bufio.NewReader(stdout) line, _, err := r.ReadLine() 希望本文所述对大家的Go语言程序设计有所帮助.

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

    目录 Pipe介绍 Go pipe简单示例 Go cmd StdoutPipe http请求处理中使用管道 利用管道提交post请求json数据 通过管道读标准输入 Go Stat 总结 本文介绍Golang pipe,以及在不同场景下的应用. Pipe介绍 pipe实现从一个进程重定向至另一个进程,它是双向数据通道,用于实现进行间通信. io.Pipe函数创建内存同步通道,用于连接io.Reader和io.Writer. 本文示例使用环境为: go versiongo version go1.

  • 详解golang执行Linux shell命令完整场景下的使用方法

    目录 1. 执行命令并获得输出结果 2. 将stdout和stderr分别处理 3. 异步执行命令 4. 执行时带上环境变量 5. 预先检查命令是否存在 6. 两个命令依次执行,管道通信 7. 按行读取输出内容 8. 获得exit code 1. 执行命令并获得输出结果 CombinedOutput() 执行程序返回 standard output and standard error func main() { cmd := exec.Command("ls", "-lah

  • 浅谈C++高并发场景下读多写少的优化方案

    目录 概述 分析 双缓冲 工程实现上需要攻克的难点 核心代码实现 简单说说golang中双缓冲的实现 相关文献 概述 一谈到高并发的优化方案,往往能想到模块水平拆分.数据库读写分离.分库分表,加缓存.加mq等,这些都是从系统架构上解决.单模块作为系统的组成单元,其性能好坏也能很大的影响整体性能,本文从单模块下读多写少的场景出发,探讨其解决方案,以其更好的实现高并发.不同的业务场景,读和写的频率各有侧重,有两种常见的业务场景: 读多写少:典型场景如广告检索端.白名单更新维护.loadbalance

  • Redis中秒杀场景下超时与超卖问题的解决方案

    目录 超时 1.redis连接超时原因 2.解决方法 超卖 1.秒杀超卖现象 2.解决方案 (1)利用乐观锁淘汰用户,解决超卖问题 (2).使用reids的 watch + multi + setnx 指令实现 在开发过程中高并发问题是很棘手的一个问题(对于博主这样的小菜鸡来说),当我们学习redis之前,知道redis是单线程运行的所以任务不会出现线程不安全问题.当我们在linux中使用ab来模拟高并发秒杀时可能会遇到两种问题,“超时和超卖”. 超时 1.redis连接超时原因 (1)虚拟机中

  • Apache Pulsar 微信大流量实时推荐场景下实践详解

    目录 导语 作者简介 实践 1:大流量场景下的 K8s 部署实践 实践 2:非持久化 Topic 的应用 实践 3:负载均衡与 Broker 缓存优化 实践 4:COS Offloader 开发与应用 未来展望与计划 导语 本文整理自 8 月 Apache Pulsar Meetup 上,刘燊题为<Apache Pulsar 在微信的大流量实时推荐场景实践>的分享.本文介绍了微信团队在大流量场景下将 Pulsar 部署在 K8s 上的实践与优化.非持久化 Topic 的应用.负载均衡与 Bro

  • 深入浅析Vue不同场景下组件间的数据交流

    正文 浅谈Vue不同场景下组件间的数据"交流" Vue的官方文档可以说是很详细了.在我看来,它和react等其他框架文档一样,讲述的方式的更多的是"方法论",而不是"场景论",这也就导致了:我们在阅读完文档许多遍后,写起代码还是不免感到有许多困惑,因为我们不知道其中一些知识点的运用场景.这就是我写这篇文章的目的,探讨不同场景下组件间的数据"交流"的Vue实现 父子组件间的数据交流 父子组件间的数据交流可分为两种: 1.父组件传

  • Vue实现购物车场景下的应用

    本文实例为大家分享了Vue在购物车场景下的应用,供大家参考,具体内容如下 购物车场景需求: 1. 商品.店铺.购物车的选择 2. 商品删除 关键代码 测试数据 var _list = [{ checked: false, goods: [{ name: "商品1", price: 23, checked: false }] }, { checked: false, goods: [{ name: "商品2", price: 20, checked: false },

  • Python实现堡垒机模式下远程命令执行操作示例

    本文实例讲述了Python实现堡垒机模式下远程命令执行操作.分享给大家供大家参考,具体如下: 一 点睛 堡垒机环境在一定程度上提升了运营安全级别,但同时也提高了日常运营成本,作为管理的中转设备,任何针对业务服务器的管理请求都会经过此节点,比如SSH协议,首先运维人员在办公电脑通过SSH协议登录堡垒机,再通过堡垒机SSH跳转到所有的业务服务器进行维护操作. 我们可以利用paramiko的invoke_shell机制来实现通过堡垒机实现服务器操作,原理是SSHClient.connect到堡垒机后开

  • 分区表场景下的 SQL 优化

    导读 有个表做了分区,每天一个分区. 该表上有个查询,经常只查询表中某一天数据,但每次都几乎要扫描整个分区的所有数据,有什么办法进行优化吗? 待优化场景 有一个大表,每天产生的数据量约100万,所以就采用表分区方案,每天一个分区. 下面是该表的DDL: CREATE TABLE `t1` ( `id` bigint(20) NOT NULL AUTO_INCREMENT, `date` date NOT NULL, `kid` int(11) DEFAULT '0', `uid` int(11)

  • java实现Linux(centos) 中docker容器下命令交互的代码(配置向导)

    开发需求: 因系统程序部署时,经常是拆分部署(多台机器) ,手工部署费时费力,且每次都要手工配置系统参数(系统提供配置向导). 如下图所示: 1)进行main容器 -> 2)执行系统配置向导 -> 3)选择服务器模式 -> 4) 选择web控制台....然后进行具体的服务器IP设置. 为了解放双手,用java实现了Linux(centos) 下docker 应用程序的命令交互. 具体代码如下: import java.io.*; /** * @author by dujiajun * @

随机推荐