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:
binary.Write
describing the size of the filenameio.WriteString
io.CopyN
My server logic is:
binary.Read
, save as N
N
bytes to get the filename, which is read into a bytes.NewBuffer(make([]byte, filenameSize))
that has String()
subsequently called on itM
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
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
import "bytes"
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.