I am evaluating libuv as a library for a C/C++ server that I am writing. The protocol is length prefixed so as soon as I can read a 32 bit integer from the stream I should be able to tell what size of buffer I should allocate. The documentation says that the uv_read_start function might be called multiple times.
UV_EXTERN int uv_read_start(uv_stream_t*, uv_alloc_cb alloc_cb, uv_read_cb read_cb);
Since I am using a length prefixed protocol, once I know the right size of the buffer I would like to allocate it and re use it for subsequent reads till I have received all my bytes. Is there an easy way to do this with libuv? Right now it seems like the uv_alloc_cb function has to take care of this. Can I associate my own buffer object with my stream object instead of putting it in a map or something? This buffer struct would contain a pointer to the actual byte array and the current position to continue writing from. It would be great if you guys can point me towards an example where
Since I am using a length prefixed protocol, I don't want to allocate a buffer on the heap at all till I can read the first 4 bytes (32 bits). Is it possible for me to allocate on the stack a buffer of size 4 and have the uv_read_cb function actually do the heap allocation? Is the uv_read_cb function invoked synchronously as part of the uv_read_start function? If it is then seems like I should be able to allocate on the stack when I know that I don't already have a buffer attached to my stream.
What I am trying to achieve in pseudocode is:
if (new connection or new request on old connection) { // New request on old connection happens when the previous length prefixed protocol message was received completely.
byte arr[4];
readBytes(byte, 4);
Figure out expected length and allocate my buffer struct.
} else if (pendingRequest on existing connection) { // An old connection which is still receiving the bytes that it is supposed to receive.
Fetch the buffer associated with this stream and re use the underlying byte array.
}
Thanks,
Rajiv
On Mon, Oct 7, 2013 at 8:14 PM, Rajiv Kurian <geet...@gmail.com> wrote:
> Thanks for the answers Ben.
>
> Why does alloc_cb need to be called asynchronously? Isn't uv_read-start
> already called asynchronously? If so then I am guessing this is only called
> when the libuv backend (epoll, kqueue etc) signals that there is a read
> interest. In that case the data to be read is already available isn't it?
> Why call alloc_cb asynchronously again?
uv_read_start() is what you as the user of libuv call to inform libuv
that from now on you want to receive incoming data. That incoming
data - when it arrives - is then read in an asynchronous fashion.
The reason you have an alloc_cb and read_cb is that libuv uses a
completion-based event model, not the readiness model of poll() and
friends. (The reason for that is that IOCP is completion-based; you
can efficiently simulate completion-based I/O with readiness-based I/O
but not the other way around.)
Completion-based means you tell the operating system "here is a
buffer, here is a socket handle, read data from one into the other and
ping me when you're done." That's why you can't make any assumptions
about the buffer you return from your alloc_cb, it may not be
processed straight away.
If you're only targeting UNIX systems, then yes, libuv basically does
read(handle, alloc_cb()) and you could optimize for that - but it's an
implementation detail that may change someday and you sacrifice
Windows portability for a minor convenience.