douxian7117 2015-04-28 19:17
浏览 153
已采纳

在Go中从服务器获取EOF作为客户端

I have some a Go client for a custom protocol. The protocol is lz4-compressed JSON-RPC with a four byte header giving the length of the compressed JSON.

func ReceiveMessage(conn net.Conn) ([]byte, error) {
    start := time.Now()
    bodyLen := 0
    body := make([]byte, 0, 4096)
    buf := make([]byte, 0, 256)

    for bodyLen == 0 || len(body) < bodyLen {
        if len(body) > 4 {
            header := body[:4]
            body = body[:4]
            bodyLen = int(unpack(header))
        }

        n, err := conn.Read(buf[:])
        if err != nil {
            if err != io.EOF {
                return body, err
            }
        }
        body = append(body, buf[0:n]...)

        now := time.Now()
        if now.Sub(start) > time.Duration(readTimeout) * time.Millisecond     {
            return body, fmt.Errorf("Timed-out while reading from socket.")
        }
        time.Sleep(time.Duration(1) * time.Millisecond)
    }

    return lz4.Decode(nil, body)
}

The client:

func main() {
    address := os.Args[1]
    msg := []byte(os.Args[2])

    fmt.Printf("Sending %s to %s
", msg, address)

    conn, err := net.Dial(address)
    if err != nil {
        fmt.Printf("%v
", err)
        return
    }

    // Another library call
    _, err = SendMessage(conn, []byte(msg))
    if err != nil {
        fmt.Printf("%v
", err)
        return
    }

    response, err := ReceiveMessage(conn)
    conn.Close()

    if err != nil {
        fmt.Printf("%v
", err)
        return
    }

    fmt.Printf("Response: %s
", response)
}

When I call it, I get no response and it just times out. (If I do not explicitly ignore the EOF, it returns there with io.EOF error.) I have another library for this written in Python that also works against the same endpoint with the same payload. Do you see anything immediately?

  • 写回答

2条回答 默认 最新

  • doucong1992 2015-04-28 20:36
    关注

    [JimB just beat me to an answer but here goes anyway.]

    The root issue is that you did body = body[:4] when you wanted body = body[4:]. The former keeps only the first four header bytes while the latter tosses the four header bytes just decoded.

    Here is a self contained version with some debug logs that works. It has some of the other changes I mentioned. (I guessed at various things that you didn't include, like the lz4 package used, the timeout, unpack, etc.)

    package main
    
    import (
            "encoding/binary"
            "errors"
            "fmt"
            "io"
            "log"
            "net"
            "time"
    
            "github.com/bkaradzic/go-lz4"
    )
    
    const readTimeout = 30 * time.Second // XXX guess
    
    func ReceiveMessage(conn net.Conn) ([]byte, error) {
            bodyLen := 0
            body := make([]byte, 0, 4096)
            var buf [256]byte
    
            conn.SetDeadline(time.Now().Add(readTimeout))
            defer conn.SetDeadline(time.Time{}) // disable deadline
            for bodyLen == 0 || len(body) < bodyLen {
                    if bodyLen == 0 && len(body) >= 4 {
                            bodyLen = int(unpack(body[:4]))
                            body = body[4:]
                            if bodyLen <= 0 {
                                    return nil, errors.New("invalid body length")
                            }
                            log.Println("read bodyLen:", bodyLen)
                            continue
                    }
    
                    n, err := conn.Read(buf[:])
                    body = append(body, buf[:n]...)
                    log.Printf("appended %d bytes, len(body) now %d", n, len(body))
                    // Note, this is checked *after* handing any n bytes.
                    // An io.Reader is allowed to return data with an error.
                    if err != nil {
                            if err != io.EOF {
                                    return nil, err
                            }
                            break
                    }
            }
            if len(body) != bodyLen {
                    return nil, fmt.Errorf("got %d bytes, expected %d",
                            len(body), bodyLen)
            }
    
            return lz4.Decode(nil, body)
    }
    
    const address = ":5678"
    
    var msg = []byte(`{"foo":"bar"}`)
    
    func main() {
            //address := os.Args[1]
            //msg := []byte(os.Args[2])
    
            fmt.Printf("Sending %s to %s
    ", msg, address)
    
            conn, err := net.Dial("tcp", address)
            if err != nil {
                    fmt.Printf("%v
    ", err)
                    return
            }
    
            // Another library call
            _, err = SendMessage(conn, msg)
            if err != nil {
                    fmt.Printf("%v
    ", err)
                    return
            }
    
            response, err := ReceiveMessage(conn)
            conn.Close()
    
            if err != nil {
                    fmt.Printf("%v
    ", err)
                    return
            }
    
            fmt.Printf("Response: %s
    ", response)
    }
    
    // a guess at what your `unpack` does
    func unpack(b []byte) uint32 {
            return binary.LittleEndian.Uint32(b)
    }
    
    func SendMessage(net.Conn, []byte) (int, error) {
            // stub
            return 0, nil
    }
    
    func init() {
            // start a simple test server in the same process as a go-routine.
            ln, err := net.Listen("tcp", address)
            if err != nil {
                    log.Fatal(err)
            }
            go func() {
                    defer ln.Close()
                    for {
                            conn, err := ln.Accept()
                            if err != nil {
                                    log.Fatalln("accept:", err)
                            }
                            go Serve(conn)
                    }
            }()
    }
    
    func Serve(c net.Conn) {
            defer c.Close()
            // skip readding the initial request/message and just respond
            const response = `{"somefield": "someval"}`
            // normally (de)compression in Go is done streaming via
            // an io.Reader or io.Writer but we need the final length.
            data, err := lz4.Encode(nil, []byte(response))
            if err != nil {
                    log.Println("lz4 encode:", err)
                    return
            }
            log.Println("sending len:", len(data))
            if err = binary.Write(c, binary.LittleEndian, uint32(len(data))); err != nil {
                    log.Println("writing len:", err)
                    return
            }
            log.Println("sending data")
            if _, err = c.Write(data); err != nil {
                    log.Println("writing compressed response:", err)
                    return
            }
            log.Println("Serve done, closing connection")
    }
    

    <kbd>Playground</kbd> (but not runnable there).

    本回答被题主选为最佳回答 , 对您是否有帮助呢?
    评论
查看更多回答(1条)

报告相同问题?

悬赏问题

  • ¥15 用stata实现聚类的代码
  • ¥15 请问paddlehub能支持移动端开发吗?在Android studio上该如何部署?
  • ¥170 如图所示配置eNSP
  • ¥20 docker里部署springboot项目,访问不到扬声器
  • ¥15 netty整合springboot之后自动重连失效
  • ¥15 悬赏!微信开发者工具报错,求帮改
  • ¥20 wireshark抓不到vlan
  • ¥20 关于#stm32#的问题:需要指导自动酸碱滴定仪的原理图程序代码及仿真
  • ¥20 设计一款异域新娘的视频相亲软件需要哪些技术支持
  • ¥15 stata安慰剂检验作图但是真实值不出现在图上