Golang pipe在不同场景下怎么远程交互
这篇“Golang pipe在不同场景下怎么远程交互”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“Golang pipe在不同场景下怎么远程交互”文章吧。
Pipe介绍
pipe实现从一个进程重定向至另一个进程,它是双向数据通道,用于实现进行间通信。
io.Pipe函数创建内存同步通道,用于连接io.Reader和io.Writer. 本文示例使用环境为:
go version
go version go1.19.3 linux/amd64
Go pipe简单示例
在实现远程交互之前,先看下面简单示例,演示如何使用io.Pipe函数:
package mainimport ( "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 mainimport ( "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 mainimport ( "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 = pwgo 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 mainimport ( "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 mainimport ( "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 CSTBytes: 43 Chunks: 1
这里通过|
操作传递date命令的输出结果,显示内容与预期一致。
Go Stat
Stat函数返回FileInfo结构体,描述文件信息。我们可以利用其检查数据是否来自终端。
package mainimport ( "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 pipe在不同场景下怎么远程交互”这篇文章的内容,相信大家都有了一定的了解,希望小编分享的内容对大家有帮助,若想了解更多相关的知识内容,请关注编程网行业资讯频道。
免责声明:
① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。
② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341