You can get surprisingly far, before it bites you, with only a fuzzy and incorrect understanding of how you should read from a TCP socket. I see this often in (failing) Protohackers solutions. Once you are over the initial hurdle of reading enough documentation to actually get a TCP session connected, there are 2 key things you need to understand:
The main misconception people have is that when they're reading from a TCP socket, they are receiving packets. This is the wrong way to think about it. If you're writing anything higher level than the TCP implementation itself, then you should forget about packets. TCP is exposed to you via a pair of byte streams.
Typically both streams are on the same file handle (they have the same file descriptor), but remember there are two underlying streams: writing puts bytes into the stream that is sent to the other side, and reading gets bytes out of the stream coming from the other side.
When you learn about networking, you learn about packets, so it is natural to assume that you'll be handling packets. When you write test programs to send short strings over localhost, it probably appears as if every write() pops out in a corresponding read(). When you test your server with netcat, it appears as if each line is sent as a discrete "packet" because the line-buffering on netcat's stdin means each line doesn't get read by netcat until you press enter, and then it all gets sent to the socket at once.