I’ve been implementing circular buffers in all kinds of flavors and purposes for years now and only with positive results. I think you can’t do serious communication without them!
A circular buffer is the best solution for your problem and for your particular case it’s very easy to implement, so there’s nothing to be scared about.
You declare your buffer as a regular array of bytes, and 2 pointers to byte that will point to elements of this array (or you can just use indexes if you don't like pointers). One pointer is for writing bytes in the array (used by the UART ISR, server, producer etc.) and the other one is for reading bytes from the array (used by the main loop, client, consumer etc.).
A) Initialize both pointers to point to first element of the array. The buffer is then empty.
B) On the producer side (ISR):
- write a byte to where the write-pointer points;
- increment write-pointer;
- if it points after the end of the array, you make it point to the beginning (that’s why it’s called “circular”);
- [optional] if it points to one location less than the read-pointer, the buffer is full (you can skip this test and continue by overwriting).
C) On the consumer side (main loop):
- if the read-pointer points to the same location as the write-pointer the buffer is empty, there’s nothing to read;
- otherwise read the byte pointed to by read-pointer;
- increment read-pointer;
- if it points after the end of the array, you make it point to the beginning (that’s why it’s called “circular”).
Of course, all rules for working with interrupts apply here too (volatility, critical resource access etc.), but otherwise there’s nothing much to it, as you can see.
Arthur