I haven't read all of this thread, so I might be missing the obvious:
Why grow an array in tiny increments?
Make a linked list of allocation buffers, each of which contains space for a a few kb of data.
Then simply traverse the list in an outer loop, filling the current buffer and keeping track of how full it is. When it is full, add a new buffer to the list and start from there. You only need to keep track of the size of the current buffer being filled and the overall amount of data recieved.
Disadvantages:
Your data is stored in a noncontiguous way, divided into blocks of several KB. Random access to the data is therefore not efficient when scattered accesses span several blocks.
You can, however traverse the list relatively easily and sequential access would not be a huge performance hit.
Advantages:
No limit to the amount of data that can be recieved (until memory fills, basically). You never copy anything at any point. Your blocksize, inclusive of link pointers can be carefully chosen to give a single block that matches the system page size. This can be an effective optimisation and help reduce framgentation.
If you really need random read access, there is an alternative.
Allocate an array of pointers that point to sequentially allocated blocks of data. These would be all the same size, again several KB each.
You simply fill the first buffer (pointed to by buffers[0]), once that is full, you allocate another, pointed to by buffers[1] etc.
Random access can then be achieved by simply breaking down the supplied index into its block number and offset into the block.
Advantages:
Likely even faster than the linked list for sequential access and dramatically faster for random access.
Disadvantages:
Unless you periodically grow your buffers array, it has an implicit limit to the amount it can hold.