Spdy流接收器正在接收零对象

时间:2018-06-19 06:36:05

标签: go spdy libchan

我使用docker的libchan库。他们的例子是这样的:

// 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信号。但如果是这样的话,为什么这个命令会是空白的呢?请帮助我了解正在发生的事情。

1 个答案:

答案 0 :(得分:2)

似乎是尝试在退出时对来自客户端的FIN ACK TCP数据包进行解码的尝试。 TCP连接已关闭,在服务器端,我们尝试阅读此内容。我们收到EOF错误,因为没有更多的输入要读取。这似乎是documentation中指定的行为:

  

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

在内部,libchan spdy使用msgpack编码器和解码器(source code)来读取此TCP数据包将调用bufio ReadByte()函数(source code),如果没有则返回错误还有更多的数据需要读取(TCP连接关闭时就是这种情况)。

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

您可以看到正在运行sudo tcpdump -i lo dst port 9323的TCP数据包交换。导致此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 documentation

  

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

     

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

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

如果您查看代码,您会看到spdy.NewSpdyStreamProvider(c, true)创建了新的spdystream连接,然后在单独的goroutine中对该连接创建了runs Serve。 spdstream的服务功能尝试读取收到的FIN ACK数据包和receives EOF(如上面引用的go文档中所指定)。然后,它退出函数和closes channels的主循环。然后,我们在接收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