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



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

似乎尝试在退出时解码来自客户端的 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。阅读器文档:

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

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

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

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

为了更详细地查看正在发生的事情,您可以设置 env 变量 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

最新更新