Приемник Spdy Stream принимает нулевой объект
Я использую 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 ответ
Кажется, это попытка расшифровки TCP-пакета FIN ACK, поступающего от клиента при выходе. 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
, Пакет FIN ACK TCP, который вызывает эту ошибку EOF:
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 байтов, возвращаемых до рассмотрения ошибки err. Это правильно обрабатывает ошибки ввода-вывода, возникающие после чтения некоторых байтов, а также оба допустимых поведения EOF.
[EDIT] Чтобы более конкретно ответить на то, что происходит за кулисами с libchan:
Если вы посмотрите в коде, вы увидите, что spdy.NewSpdyStreamProvider(c, true)
создает новое соединение spdystream, а затем запускает Serve для этого соединения в отдельной программе. Функция обслуживания spdstream пытается прочитать полученный пакет FIN ACK и получает EOF (как указано в документации, приведенной выше). Затем он выходит из основного цикла функции и закрывает каналы. Затем мы получаем нашу ошибку 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