1

我正在使用libchandocker 库。他们的例子是这样的:

// client.go
package main

import (
    "log"
    "io"
    "net"
    "os"
    "github.com/docker/libchan"
    "github.com/docker/libchan/spdy"
)

type RemoteCommand struct {
    Cmd        string
    Args       []string
    Stdin      io.Writer
    Stdout     io.Reader
    Stderr     io.Reader
    StatusChan libchan.Sender
}

type CommandResponse struct {
    Status int
}

func main() {
    var client net.Conn
    client, err := net.Dial("tcp", "127.0.0.1:9323")
    if err != nil {
        log.Fatal(err)
    }

    p, err := spdy.NewSpdyStreamProvider(client, false)
    transport := spdy.NewTransport(p)
    sender, err := transport.NewSendChannel()
    if err != nil {
        log.Fatal(err)
    }

    receiver, remoteSender := libchan.Pipe()

    command := &RemoteCommand{
        Cmd:        os.Args[1],
        Args:       os.Args[2:],
        Stdin:      os.Stdin,
        Stdout:     os.Stdout,
        Stderr:     os.Stderr,
        StatusChan: remoteSender,
    }

    err = sender.Send(command)
    if err != nil {
        log.Fatal(err)
    }

    response := &CommandResponse{}
    err = receiver.Receive(response)
    if err != nil {
        log.Fatal(err)
    }

    os.Exit(response.Status)
}

这是服务器:

// server.go
package main

import (
    "log"
    "net"
    "io"
    "os/exec"
    "syscall"
    "github.com/docker/libchan"
    "github.com/docker/libchan/spdy"
)

type RemoteReceivedCommand struct {
    Cmd        string
    Args       []string
    Stdin      io.Reader
    Stdout     io.WriteCloser
    Stderr     io.WriteCloser
    StatusChan libchan.Sender
}

type CommandResponse struct {
    Status int
}

func main() {
    var listener net.Listener
    var err error
    listener, err = net.Listen("tcp", "localhost:9323")
    if err != nil {
        log.Fatal(err)
    }

    for {
        c, err := listener.Accept()
        if err != nil {
            log.Print("listener accept error")
            log.Print(err)
            break
        }

        p, err := spdy.NewSpdyStreamProvider(c, true)
        if err != nil {
            log.Print("spdy stream error")
            log.Print(err)
            break
        }
        t := spdy.NewTransport(p)

        go func() {
            for {
                receiver, err := t.WaitReceiveChannel()
                if err != nil {
                    log.Print("receiver error")
                    log.Print(err)
                    break
                }
                log.Print("about to spawn receive proc")
                go func() {
                    for {
                        command := &RemoteReceivedCommand{}
                        err := receiver.Receive(command)
                        log.Print("received command")
                        log.Print(command)
                        if err != nil {
                            log.Print("command error")
                            log.Print(err)
                            break
                        }

                        cmd := exec.Command(command.Cmd, command.Args...)
                        cmd.Stdout = command.Stdout
                        cmd.Stderr = command.Stderr

                        stdin, err := cmd.StdinPipe()
                        if err != nil {
                            log.Print("stdin error")
                            log.Print(err)
                            break
                        }
                        go func() {
                            io.Copy(stdin, command.Stdin)
                            stdin.Close()
                        }()

                        log.Print("about to run the command")
                        res := cmd.Run()
                        command.Stdout.Close()
                        command.Stderr.Close()
                        returnResult := &CommandResponse{}
                        if res != nil {
                            if exiterr, ok := res.(*exec.ExitError); ok {
                                returnResult.Status = exiterr.Sys().(syscall.WaitStatus).ExitStatus()
                            } else {
                                log.Print("res")
                                log.Print(res)
                                returnResult.Status = 10
                            }
                        }
                        err = command.StatusChan.Send(returnResult)
                        if err != nil {
                            log.Print(err)
                        }
                    }
                }()
            }
        }()


    }
}

当我运行服务器并与客户端发送消息时:

$ ./client /bin/echo "hello"

我在服务器日志中看到了这个输出:

2018/06/18 23:13:56 about to spawn receive proc
2018/06/18 23:13:56 received command
2018/06/18 23:13:56 &{/bin/echo [hello] 0xc4201201b0 0xc42023c030 0xc42023c090 0xc420186080}
2018/06/18 23:13:56 about to run the command
2018/06/18 23:13:56 received command
2018/06/18 23:13:56 &{ [] <nil> <nil> <nil> <nil>}
2018/06/18 23:13:56 command error
2018/06/18 23:13:56 EOF

我的服务器接收到带有echo命令的消息并成功执行。但是,它也会收到一个空命令,然后抛出一个 EOF:

2018/06/18 23:13:56 &{ [] <nil> <nil> <nil> <nil>}
2018/06/18 23:13:56 command error
2018/06/18 23:13:56 EOF

为什么命令是空字符串?

我的怀疑是客户端退出然后发送exit信号。但如果是这样的话,为什么命令会是空白的呢?请帮助我了解发生了什么。

4

1 回答 1

2

似乎是尝试在退出时尝试解码来自客户端的 FIN ACK TCP 数据包。TCP 连接正在关闭,在服务器端我们尝试读取它。我们得到 EOF 错误,因为没有更多的输入要读取。这似乎是 文档中指定的行为:

EOF 是当没有更多可用输入时 Read 返回的错误。函数应该只返回 EOF 来表示输入的优雅结束。如果 EOF 在结构化数据流中意外发生,则相应的错误是 ErrUnexpectedEOF 或提供更多详细信息的其他错误。

在底层,libchan spdy 使用 msgpack 编码器和解码器(源代码),为了读取这个 TCP 数据包将调用 bufio ReadByte() 函数(源代码),当没有更多数据可以读取时返回错误(并且TCP连接关闭时就是这种情况)。

// ReadByte reads and returns a single byte.
// If no byte is available, returns an error.
func (b *Reader) ReadByte() (byte, error) {
...

您可以看到 TCP 数据包交换正在运行sudo tcpdump -i lo dst port 9323。导致此 EOF 错误的 FIN ACK TCP 数据包:

18:28:23.782337 IP localhost.47574 > localhost.9323: Flags [F.], seq 272, ack 166, win 342, options [nop,nop,TS val 73397258 ecr 73397258], length 0

我认为这种行为是正常的,应该在代码中处理 EOF 错误。该命令是空白的,因为客户端没有发送任何特定的命令,只是正在关闭流。

另外 - 来自 io.Reader文档

当 Read 在成功读取 n > 0 个字节后遇到错误或文件结束条件时,它会返回读取的字节数。它可能会从同一次调用中返回(非零)错误,或者从后续调用中返回错误(并且 n == 0)。这种一般情况的一个例子是,在输入流末尾返回非零字节数的 Reader 可能会返回 err == EOF 或 err == nil。下一次读取应该返回 0,EOF。

在考虑错误错误之前,调用者应始终处理返回的 n > 0 字节。这样做可以正确处理读取某些字节后发生的 I/O 错误以及允许的 EOF 行为。

[编辑]更具体地回答 libchan 幕后发生的事情:

如果您查看代码,您会看到spdy.NewSpdyStreamProvider(c, true)创建新的 spdystream 连接,然后在单独的 goroutine 中在该连接上运行 Serve 。spdstream 的服务功能尝试读取接收到的 FIN ACK 数据包并接收 EOF(如上面引用的 go 文档中所述)。然后,它退出函数的主循环并关闭通道。然后,我们在 Receive here上收到 EOF 错误。

为了更详细地查看发生了什么,您可以设置环境变量 DEBUG=true

$ export DEBUG=true
$ ./server 

输出:

127.0.0.1:57652
2018/06/22 12:24:12 (0xc4200a42c0) Add stream frame: 1 
2018/06/22 12:24:12 (0xc4200a42c0) (0xc42016c000) Stream added, broadcasting: 1
2018/06/22 12:24:12 (0xc4200a42c0) Add stream frame: 3 
2018/06/22 12:24:12 (0xc4200a42c0) (0xc42016c0a0) Stream added, broadcasting: 3
2018/06/22 12:24:12 (0xc4200a42c0) Add stream frame: 5 
2018/06/22 12:24:12 (0xc4200a42c0) (0xc42016c140) Stream added, broadcasting: 5
2018/06/22 12:24:12 (0xc4200a42c0) Add stream frame: 7 
2018/06/22 12:24:12 (0xc4200a42c0) (0xc42016c1e0) Stream added, broadcasting: 7
2018/06/22 12:24:12 about to spawn receive proc
2018/06/22 12:24:12 trying to receive
2018/06/22 12:24:12 (0xc4200a42c0) Add stream frame: 9 
2018/06/22 12:24:12 (0xc4200a42c0) (0xc42016c280) Stream added, broadcasting: 9
2018/06/22 12:24:12 (0xc4200a42c0) Data frame received for 1
2018/06/22 12:24:12 (0xc42016c000) (1) Data frame handling
2018/06/22 12:24:12 (0xc42016c000) (1) Data frame sent
2018/06/22 12:24:12 received command
2018/06/22 12:24:12 &{/bin/echo [hello] 0xc420156570 0xc4201565a0 0xc420156120 0xc42013c4a0}
2018/06/22 12:24:12 about to run the command
2018/06/22 12:24:12 (0xc42016c140) (5) Writing data frame
2018/06/22 12:24:12 (0xc42016c140) (5) Writing data frame
2018/06/22 12:24:12 (0xc42016c1e0) (7) Writing data frame
2018/06/22 12:24:12 (0xc42016c280) (9) Writing data frame
2018/06/22 12:24:12 trying to receive
2018/06/22 12:24:12 (0xc4200a42c0) EOF received
2018/06/22 12:24:12 (0xc4200a42c0) (0xc42016c000) Stream removed, broadcasting: 1
2018/06/22 12:24:12 (0xc42016c000) (1) Writing data frame
2018/06/22 12:24:12 received command
2018/06/22 12:24:12 &{ [] <nil> <nil> <nil> <nil>}
2018/06/22 12:24:12 command error
2018/06/22 12:24:12 EOF
于 2018-06-21T16:45:19.050 回答