#Why unsized buffer generates a infinite for loop while reading from tcp connection?

16 messages · Page 1 of 1 (latest)

harsh token
#

Hello everyone! I'm building a simple tcp chat application where I read data from connections and broadcast it to the other active connections in the session.

I have the following function which is spawned as** go routine** for each incoming connection in order to read data from sent by the connection stream:


func read(conn net.Conn, msgCh chan Message) {
    buffer := []byte{}

    for {
        _, err := conn.Read(buffer)

        if err != nil {
            fmt.Println(err)
        }

          message := NewMessage(string(buffer), conn.RemoteAddr().String())
          msgCh <- message

                fmt.Printf("[MESSAGE SENT] %#v", message)
        buffer = []byte{}
    }
}

The issue comes because it starts an infinite loop where just reading empty content.

I'm curious about why this happens. Also I made a little bit of research and I noticed that if I put some size on buffer initialization (buffer := make([]byte{}, 1024), it works; but I'm not sure why.

kindred island
#

I'm not sure what you are expecting to happen, a slices is only a pointer around data

#

so you try to read 0 bytes, TCP gives you back 0 bytes and repeats

#

You need to preallocate a buffer of a certain size if you want anything to happen

#

you should also note that TCP is not a message oriented protocol, on the wire there is no real distinction between the beging and start of a message, so a protocol like you wrote would confuse messages together or split them appart

#

like there is no 1 to 1 correspondance between a .Write call and a .Read call.
Writes can be split into multiple packets and packets are merged back on the receive side.

#

For a chat application I would do something like this.
First wrap the receiver in a bufio.Reader so you can read bytes per bytes.

Then on the server side before sending a message you write a uint16 uint32 uint64 or varuint, whichever one you like. Which indicates the length of the message.
Then on the client side you first read 2, 4, 8 or N bytes from the bufio which you know how long it needs to be because either it's fixed or varuint are self decodable.

Then once you know the length you can read that many bytes from the connection. You might need some limit because if you have uint32 someone could send a 4GiB "big" message and either use lots of ram if not crash the computer.

#

The point is that TCP is a contiguous flow of data, but you by adding length you can follow each length elements to know how big the next piece of data is.

harsh token
#

Thanks! I got it.

About my first try; I just thought conn.Read should be able to append (?) incoming data in my empty buffer slice, I didn't notice conn.Read just mutates internal slice with the content incoming instead append, though.

Thanks for the idea about handling messages with an specific length, TCP is a stream protocol and I understand big data could not follow a particular order. However, I am just taking a look about go routines and Golang fundamentals, so I'm not sure I'm going to implement that feature!

kindred island
harsh token
#

Oh, one last question: What could be the best way to clear the buffer?

In the previous code I'm doing buffer := make([]byte{}, 1024) after incoming message processing. But I think it is allocating a new buffer each time (I'm not sure, though).
Is there any way to reuse the existent buffer?

I've read some about bytes.Buffer and Buffer.Reset() but I'm not sure which is the optimal way to reuse it

kindred island
#

make([]byte{}, 1024) isn't valid syntax

#

I think

#

probably

#

yeah you can reuse the buffer, just make the slice exists outside of the loop