Приемник 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
Другие вопросы по тегам