Perhaps you could implement a thread to maintain a 'ring-buffer'. The thread would listen for incoming data, and store the data in the buffer. The thread could also parse the data received to delineate when each packet has been fully received.
Then, perhaps your code could offer a suite of functions to the caller. Such as:
/* Initialize your ring-buffer, and start listening for packets, etc. */
int STEVE_Initialize();
/* Returns the number of fully recieved packets ready for processing. */
int STEVE_PacketsReadyCount();
/* Read a full packet from the ring-buffer and return it to the caller. */
int STEVE_FetchNextPacket();
/* Stop listening for packets, free ring-buffer, etc. */
int STEVE_Terminate();
POLL METHOD Given such an implementation, the caller can use 'Steve_PacketsReadyCount()' to implement a polling loop. When a packet is ready, the caller would call 'STEVE_FetchNextPacket()' to obtain the next full packet (at which time, it would be removed from the ring-buffer).
SIGNAL METHOD A more elaborate implementation might be to use system signals, such as the USR1 signal, to signal the caller that a full packet is ready. The caller would alerted to the complete arrival of a full packet from a caller-implemented signal handler.
SEMAPHORE METHOD Perhaps your code could provide a semaphore to the client that could be used by the client to "sleep" until a full packet arrives.
There are many more methods that might be used, including some sort of a message queue implementation. However it is designed, perhaps the most important input to the architecture is "what would be easiest for the caller". In order to more fully answer your question, more detail about the needs of the caller are required.