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