A TCP peer can reach end-of-stream on incoming data while its outgoing stream remains usable. The event is directional: a FIN closes one side’s sending direction after previously queued bytes, but it does not require the opposite direction to close at the same instant.

That property is easy to hide behind APIs that expose a connection as one object with a single close operation. At the protocol boundary, however, TCP carries two byte streams in opposite directions. A half-close makes the distinction visible and gives application protocols a useful signal: one participant can state that its request body is complete while still accepting a response.

End-of-stream belongs to one receive path

Suppose client C sends a request to server S and then performs a write-side shutdown. TCP eventually carries C’s FIN after the bytes C submitted before that shutdown. When S consumes all preceding bytes, its next read reports end-of-stream according to the socket API.

That receive-side event says that C will send no additional bytes on this TCP connection. It does not say that S has finished sending.

The connection can therefore occupy an asymmetric state:

C -> S: request bytes, then FIN
C <- S: response bytes continue

S can process the complete request and write a response over its still-open sending direction. Later, S can finish its own stream, causing C to observe end-of-stream after the response bytes.

This behavior gives EOF a narrower meaning than “the connection is gone.” EOF is evidence about the remote peer’s sending direction. Treating it as a universal signal to abandon both directions can discard valid data that the protocol permits after the first FIN.

A FIN is ordered after preceding bytes

TCP presents an ordered byte stream. A FIN consumes sequence space and is sequenced relative to data, so the receiving TCP does not expose the stream as finished before earlier stream bytes have been accounted for in order.

For an application protocol that defines the request as “all bytes until EOF,” this ordering matters. The receiver can accumulate bytes until its read side reaches EOF and treat that boundary as the request terminator. No sentinel byte pattern is needed inside the payload.

The boundary still carries no record structure beyond stream completion. TCP does not mark where fields, messages, or application frames end while the stream remains open. A protocol that needs several messages on one connection still needs its own framing mechanism, such as a length field, delimiter with escaping rules, or a grammar that determines message extent.

Using EOF as framing therefore has a specific consequence: the sender cannot place another request after that EOF on the same sending direction. The half-close is suitable when completion of that stream itself is the delimiter.

Socket shutdown and socket close express different intent

Many socket interfaces distinguish shutting down one direction from closing the descriptor or socket object. The names vary, but the semantic split is common: a write-side shutdown says that no more application bytes will be sent, while leaving the receive path available.

A full close has broader local consequences. Depending on the operating system, socket options, descriptor sharing, buffered data, and language runtime, the exact close behavior can involve details beyond a simple pair of FIN messages. Application code that needs the half-close property should therefore use the platform operation documented for directional shutdown rather than assume that a generic close preserves the receive path.

This distinction also affects abstractions layered above sockets. A stream wrapper may map its own close method to closing the underlying socket, making further reads impossible even though TCP itself could have supported them. Another API may expose independent reader and writer halves. The protocol capability and the library capability are separate facts.

A design that relies on half-close needs both: TCP semantics that permit independent directions and an API path that retains access to the receiving direction after the sender is finished.

EOF can be protocol data

An application read that returns EOF is often handled as transport cleanup. In an EOF-delimited protocol, it is part of the protocol grammar.

Consider a service that accepts an arbitrary binary document and returns a digest. If the request has no declared length and the protocol assigns the entire client-to-server stream to one document, the client can send the document and half-close its write side. The server’s EOF observation then supplies the document boundary. The server can compute and return the digest without requiring the client to keep its write side open.

This model has a clean invariant: every byte before EOF belongs to the request, and no request byte can follow it.

It also constrains reuse. Since EOF terminates the client-to-server stream for that connection, the same connection cannot carry another client request afterward. Protocols designed for persistent multiplexing or repeated request-response exchanges need boundaries that do not consume the stream itself.

The choice is not between valid and invalid framing. It is between different protocol shapes. EOF framing can be exact for a one-request stream, while being structurally incompatible with repeated messages in the same direction.

Half-close is not failure detection

A peer receiving FIN has evidence that the remote TCP endpoint has finished its sending direction. That is not equivalent to evidence that the remote process is healthy, that an application transaction committed, or that all prior effects succeeded.

Likewise, absence of FIN does not prove that more bytes will arrive. A process can stall, a network path can stop delivering packets, or a host can become unreachable without producing a timely orderly shutdown. TCP retransmission and timeout behavior operate below the application, and applications with bounded waiting requirements still need explicit timeout or deadline policy.

An abrupt reset also has different semantics from orderly end-of-stream. APIs commonly report a reset as an error rather than normal EOF, although exact reporting is platform-specific. Protocol code should not collapse every terminal read result into the same application event when the distinction affects whether a complete request was received.

For EOF-delimited input, orderly EOF can mean “the sender declared this stream complete.” A transport error before that boundary can instead mean that the application cannot establish that the intended request was fully delivered.

Response completion remains independent

After C half-closes its sending direction, C still needs a rule for response completion. The server might return a fixed-size value, a length-prefixed message, a self-delimiting encoding, or bytes terminated by the server’s own EOF.

If the response is also EOF-delimited, the full exchange becomes symmetric in sequence rather than simultaneous in closure:

C sends request
C ends C -> S stream
S observes request EOF
S sends response
S ends S -> C stream
C observes response EOF

No step requires C to send more bytes after its first half-close. No step requires S to stop sending merely because C has stopped. Each stream reaches its own terminal boundary.

This pattern also shows the difference between protocol completion and connection lifetime. The application can define completion around directional EOF events, while TCP maintains enough connection state to deliver and acknowledge remaining bytes. The endpoint state machine exists to preserve those directional transitions until both sides have completed or another terminal condition intervenes.

Directionality is part of the interface contract

A network abstraction that exposes only “connected” and “closed” states can be adequate for protocols that never use half-close. It becomes lossy when a protocol assigns meaning to independent stream completion.

The missing state is not an implementation curiosity. It changes what callers can express. A caller may need to declare “no more request bytes” without also declaring “I no longer accept response bytes.” Combining those statements into one operation removes a protocol transition that TCP supports.

This is one reason transport wrappers deserve semantic scrutiny at their boundaries. Convenience APIs can simplify lifecycle management while also narrowing the state model visible to application code. That narrowing is harmless only when the application protocol does not depend on the omitted states.

TCP half-close exposes the underlying model with unusual clarity: a connection is not a single pipe that flips from open to closed. It is a paired exchange of ordered byte streams, and completion can travel in each direction at a different time. Protocols that use that fact can turn directional EOF into an exact message boundary without confusing it with the end of communication as a whole.