不必要的空格添加到通过TCP发送的字符串中

I'm attempting to learn Go, and the first thing I've tried is sending files between a client and server using TCP. I set up a connection using net.Listen, and connect using net.Dial. My client logic is:

  1. Send an int64 using binary.Write describing the size of the filename
  2. Send the filename using io.WriteString
  3. Send an int64 describing the size of the file
  4. Send the file using io.CopyN

My server logic is:

  1. Read 8 bytes using binary.Read, save as N
  2. Read N bytes to get the filename, which is read into a bytes.NewBuffer(make([]byte, filenameSize)) that has String() subsequently called on it
  3. Read 8 bytes to get the filesize, save as M
  4. io.CopyN from the connection into a new file object for M bytes.

My problem is something totally baffling to me, which I haven't been able to solve or understand, and for which I can find no discussion or solution on Google or SO: even though the filename length is transmitted accurately, the server always receives a filename of twice the expected length, where the first half is whitespace. Even more bafflingly, using strings.TrimLeft(filename, " ") never works, even though it works for strings I create myself.

So I have absolutely no idea what's going on, and nothing in the docs, SO, Google, go-nuts, etc mentions anything that could seem relevant. No idea how to move forward or think about this problem, I'd love some help.

My server handler:

func handle(conn net.Conn) {
    defer conn.Close()
    conn.SetReadDeadline(time.Now().Add(time.Second * 30))

    var filenameSize int64
    err := binary.Read(conn, binary.LittleEndian, &filenameSize)
    check(err)

    filename := bytes.NewBuffer(make([]byte, filenameSize))
    bytesRead, err := io.CopyN(filename, conn, filenameSize)
    fmt.Printf("Expected %d bytes for filename, read %d bytes
", filenameSize, bytesRead)
    str := filename.String()
    fmt.Println(strings.TrimLeft(str, " "))

    var filesize int64
    err = binary.Read(conn, binary.LittleEndian, &filesize)
    check(err)
    fmt.Printf("Expecting %d bytes in file
", filesize)

    f, err := os.Create(str)
    check(err)
    bytesWritten, err := io.CopyN(f, conn, filesize)
    check(err)
    fmt.Printf("Transfer complete, expected %d bytes, wrote %d bytes", filesize, bytesWritten)
    if filesize != bytesWritten {
        fmt.Printf("ERROR! File doesn't match expected size!")
    }
}

My client:

func main() {
    name := "test_file.doc"

    conn, err := net.Dial("tcp", "localhost:8250")
    check(err)

    length := int64(len(name))
    err = binary.Write(conn, binary.LittleEndian, length)
    check(err)

    bytes, err := io.WriteString(conn, name)
    check(err)
    if bytes != len(name) {
        fmt.Printf("Error! Wrote %d bytes but length of name is %d!
", bytes, length)
    }

    f, err := os.Open(name)
    check(err)

    stat, err := f.Stat()
    check(err)

    filesize := stat.Size()
    err = binary.Write(conn, binary.LittleEndian, filesize)
    check(err)

    bytesWritten, err := io.CopyN(conn, f, filesize)
    check(err)
    if bytesWritten != filesize {
        fmt.Printf("Error! Wrote %d bytes but length of file is %d!
", bytes, stat.Size())
    }
}

The Go Programming Language Specification

Allocation

Making slices, maps and channels

Call             Type T     Result

make(T, n)       slice      slice of type T with length n and capacity n
make(T, n, m)    slice      slice of type T with length n and capacity m

Package bytes

import "bytes"

func NewBuffer

func NewBuffer(buf []byte) *Buffer

NewBuffer creates and initializes a new Buffer using buf as its initial contents. The new Buffer takes ownership of buf, and the caller should not use buf after this call. NewBuffer is intended to prepare a Buffer to read existing data. It can also be used to size the internal buffer for writing. To do that, buf should have the desired capacity but a length of zero.

In most cases, new(Buffer) (or just declaring a Buffer variable) is sufficient to initialize a Buffer.


bytes.NewBuffer can be used to size the internal buffer for writing. To do that, buf should have the desired capacity but a length of zero. For example, with a length of zero and a capacity of filenameSize,

filename := bytes.NewBuffer(make([]byte, 0, filenameSize))

In error, you allocated buf with a length and capacity of filenameSize,

filename := bytes.NewBuffer(make([]byte, filenameSize))

The line in the server handler

filename := bytes.NewBuffer(make([]byte, filenameSize))

is wrong, repace it by

var filename bytes.Buffer

The expression make([]byte, filenameSize) creates an slice with initial length filenameSize filled with the null value of type byte, so 0. bytes.NewBuffer creates a buffer with initial contents of this slice and will append to this. So you don't receive to much, you start with too much.

See golang language spec for make. See package bytes doc about bytes.newBuffer, it explicitly states that you need length zero but positive capacity if you intend to hand a buffer for some preallocation scheme.