TCP is the notion of a reliable, in-order byte stream over a transport which doesn't do bytes, and is neither in-order nor reliable. It is both a specification, and an actual implementation (which may be very different on different systems!). A software emulation of some kinda abstract idea, if you will.
Generally, from a relatively naive point of view, TCP simply grabs whatever you throw at it, divides it into segments (for small data, there will be just a single segment), and sends them out one after the other, waiting for an acknowledgement for each segment before sending the next, and re-sending a segment after "some time" if no acknowledgement came in. There's some checksums and sequence numbers added, too, blah blah.
The other end puts the segments together again in the correct order, and provides "bytes" to an application that asks for it via an obscure "socket" (which is more or less the same as a file descriptor), without anyone really knowing where the bytes came from, or what was necessary to provide them.
Now, if the world was perfect, if bandwidth was infinite, if routers didn't have queues, and if round trip times were zero, then this would be a perfectly working approach. Alas, that's not what the world looks like.
For that reason, TCP does a couple of things differently such as sending out several segments before blocking to wait for acknowledgements (with a diversity of different algorithms that adjust the window in different ways to provide best possible throughput without dropping too many packets on the wire).
Also, it tries to avoid sending single-byte (or very-few-byte) datagrams because that's a very obnoxious thing. This is done by what's called Nagle's algorithm, and it is actually pretty simple. Tiny amounts of data are bufferend and held back until the previously sent packet was acknowledged (which is what TCP would normally have done anyway, without delayed ACKs). This automatically rate-limits tiny sends and batches data together without the need for a lot of extra logic. Unless you explicitly specify
TCP_NODELAY for a socket, that's the behavior that you normally get on every reasonable system. Well, in theory it's that simple, in practice, with delayed ACKs, it's a tidbit more complicated, but let's ignore that.
On the other hand, every now and then, TCP sends out data, with or without delay at its own discretion, based on unknown, unspecified, obscure metrics. Data size and time are parameters that play a role, without you or me, or anyone knowing exactly. Sending out anything that's present in the buffers every so-and-so-many hundred milliseconds is a common approach. While this sounds rather awful, it's actually good enough for 99.9% of all people, 99.9% of the time.
Remember, TPC is not a guaranteed realtime (or zero-delay) magical delivery system. It's a simulation of a reliable, ordered byte stream. Where "reliable" doesn't even mean that you have a guarantee that data will arrive at the other end. This is a guarantee that no protocol could possibly provide! Imagine what happens if I pull out the network cable, how do you guarantee that data will arrive?
You only have the guarantee that TCP will try its very best (by re-sending, if necessary), and you will know in case it didn't work. Other than with raw IP datagrams or UDP, for example. Where you boldly send out stuff, and nobody tells you whether or not something was sent and received at all, or maybe dropped, lost, whatever.
Sometimes, TCP sending your data on its own behalf is just not what you want, though! Sometimes, you have several smaller pieces of data that you preferrably want to have sent in one block. A "typical" server reply that starts with some header data, followed by the actual contents is an example of that. Sure enough, you don't want TCP to send out the pretty useless header alone, and then send out the actual contents later, only because you need two or three calls to
send, and someone tries to be extra smart while you're done half-way.
Partial datagrams waste bandwidth and are more likely to cause a packet getting dropped somewhere on the internet (more packets on the wire means more packets dropped, inevitably).
That is the reason why such things as
TCP_NOPUSH in BSD) exist. This is basically the exact opposite of
TCP_NODELAY, you're telling the network stack that you are going to add a couple of chunks of data one-by-one, and your expectation is that TCP doesn't in the mean time send out a half-MTU sized or even smaller datagram, or whatever.
The actual behavior, again, differs depending on what OS you run on. BSD, for example, will buffer data until a max-sized datagram worth of data is in the buffers (then send a MTU-sized datagram, and buffer again), or until you set
TCP_NOPUSH to zero again, or until you close the socket (at which time it will send out the remainder). Linux, on the other hand side, will strictly comply with your request for 200 milliseconds, and then, devil may care, send out partial data anyway.
As a sidenote, the fact that TCP may send out remaining data upon calling
close is a good reason to actually check the return code (which many people don't do, who cares what
close may return!). Unless you do check, there's no way of knowing what has been going on, no way of being sure your guarantees are what you think they are.