skip to content

What is a TCP half-close, and what can each endpoint still do after one side has sent its FIN?

level: middleimportance: should knowfreq 32%

answer

  1. two streams, closed separately
  2. done sending, still reading
  3. CLOSE means no more to send
  4. shutdown for writing, not close

basics

~10 s

A TCP half-close ends one direction only. The side that sent FIN promises no more data but keeps receiving; the peer, in CLOSE-WAIT, may keep sending until it closes too.

solid answer

~50 s

TCP carries two independent byte streams, and RFC 9293 treats CLOSE as simplex: it means "I have no more data to send", not "I will not receive". After endpoint A sends `FIN` and it is acknowledged, A sits in `FIN-WAIT-2` and must go on reading and acknowledging; B sits in `CLOSE-WAIT`, sees end of stream once A's earlier data is delivered, and may send as much as it likes before sending its own `FIN`. In the POSIX socket model this is `shutdown()` for the write direction, which sends the `FIN` while the socket stays readable. The classic use is signalling end of input: a client sends its whole request, half-closes, and reads the answer until the server's `FIN`. Some stacks implement only a half-duplex close, where a closed socket can no longer read; RFC 9293 then says unread or late-arriving data SHOULD draw an `RST`.

go deeper

for a junior

Recall that TCP closes each direction separately: the side that sent FIN stops sending but can still receive, and the other side may keep sending.

for a middle

Name the states on each side during a half-close, explain why FIN's place in the sequence space guarantees end of stream comes after the data, and contrast shutdown with close.

for a senior

Show how half-close is used to mark end of input and how it fails in practice: readers that never close, unread data turning a close into a reset.

for a principal

When designing a protocol over TCP, decide whether to rely on half-close for framing or to use explicit length or delimiters, weighing simplicity against endpoints and path devices that handle half-closed flows poorly.

## Two streams, closed separately A TCP connection is **full duplex**: two independent byte streams, A→B and B→A, each with its own sequence numbers and acknowledgments. Opening creates both at once, but closing does not have to end both at once. RFC 9293, the current TCP specification, says so directly: since the two directions are closed independently, a connection can be **half closed**, closed in one direction only, and a host may continue sending in the open direction. The key is what RFC 9293 means by the CLOSE operation: **"I have no more data to send."** It adds that a user who closes "may continue to RECEIVE until the TCP receiver is told that the remote peer has CLOSED also". A `FIN` segment is simply how that promise travels: it sits in the byte stream after the last data byte and occupies one sequence number. ## The states during a half-close Suppose A's application finishes sending and closes its direction: 1. A sends `FIN` and enters `FIN-WAIT-1`. It can still receive. 2. B acknowledges the `FIN`, enters `CLOSE-WAIT` and tells its application the peer is closing. 3. A receives the ACK and enters `FIN-WAIT-2`. The connection is now half closed. 4. B keeps sending for as long as it needs; A keeps receiving and acknowledging. 5. B's application closes; B sends its `FIN` and enters `LAST-ACK`. A acknowledges and enters `TIME-WAIT`. | | A (sent FIN first) | B (received FIN) | |---|---|---| | State during the half-close | `FIN-WAIT-2` | `CLOSE-WAIT` | | May send data | no | yes | | Must receive and acknowledge | yes | yes (acknowledgments for A's stream) | | What ends this phase | B's `FIN` arriving | B's application closing | `FIN-WAIT-2` and `CLOSE-WAIT` are not error states. They are the two halves of a half-closed connection, and the connection can legitimately stay in them for as long as B has data to send. ## Using it: marking the end of input Half-close lets an application say "that is everything" without a length prefix or a delimiter: 1. The client writes its whole request. 2. The client shuts down its write direction; in the POSIX socket model that is `shutdown()` with the write side, which sends a `FIN` but leaves the socket open for reading. 3. The server reads until its `recv` returns zero bytes. Because the `FIN` is sequenced after the data, this end of stream appears only after every byte of the request has been delivered. 4. The server computes and sends the whole response, then closes. 5. The client reads until its own end of stream. RFC 9293 also says the stack will signal the application that the peer has closed even if no receive is outstanding, so the reader is not left guessing. ## Close versus shutdown The protocol has one CLOSE; the socket API has two calls that produce a `FIN`: - `shutdown()` for writing affects **one direction** and keeps the descriptor usable for reading. - `close()` releases the descriptor. After it, the application can no longer read, even though TCP would still deliver data in the open direction. That second case is what RFC 9293 §3.6.1 calls a **half-duplex close**, which a host MAY implement. If an application closes that way while received data is still unread, or data arrives after the close, the stack SHOULD send `RST` rather than `FIN`, to show that data was lost. A program that "closes to signal end of request" and then expects to read the answer is relying on the wrong call. ## Where half-close goes wrong - **The reader never notices end of stream.** B's application never checks for the zero-byte read and never closes, so B stays in `CLOSE-WAIT` and A in `FIN-WAIT-2` indefinitely. RFC 9293 defines no timer that ends either state; stacks that time out a `FIN-WAIT-2` connection nobody owns are making an implementation choice. - **Unread data at close.** As above, the stack's reset replaces the graceful close and the peer sees an error. - **Assuming FIN means "go away".** A peer that treats an incoming `FIN` as a full shutdown and stops sending breaks protocols that deliberately half-close. - **Stateful devices on the path** may give up on a long-idle half-closed flow sooner than the endpoints expect; that is a property of the device, not of TCP. ## Half-close versus abort A half-close keeps every guarantee: data in both directions is delivered and acknowledged, and the final state is reached by two `FIN`s. An abort (`RST`) ends both directions at once and discards whatever was queued. A half-close is a sentence finished while the other speaker continues; an abort is the line going dead.

  • How does a TCP application learn that its peer has half-closed the connection?
    The peer's `FIN` is sequenced after its last data byte, so the stack delivers all earlier data first and then signals end of stream. RFC 9293 says the stack will signal that the remote peer has closed even when no receive is outstanding. In the POSIX socket model, the next `recv` returns zero bytes.
  • What should a TCP stack do if an application closes the socket while received data is still unread?
    RFC 9293 §3.6.1 lets a host implement a half-duplex close, where a closed socket can no longer read. In that case, closing with received data still pending, or receiving data after the close, SHOULD produce an `RST` to show the peer that data was lost, instead of a graceful `FIN`.

It is like switching off your microphone on a two-way radio call but keeping your earpiece in: you have said everything you are going to say, and you still hear the other person, who can keep talking until they switch off their own microphone.

saying these in an interview costs you the question

  • After sending FIN, a TCP endpoint can no longer receive any data
  • Half-close needs a special flag distinct from the ordinary FIN
  • Calling close and shutting down the write side do exactly the same thing
  • CLOSE-WAIT means the connection is finished and only waiting for a timer
  • The reader can see end of stream before the peer's last data bytes