Writing to a server: request/response turns
◈ 5 cardsDrive a multi-turn text protocol with a dictionary client: read the banner, send a CRLF-framed command, flush, read to the terminator, quit — and see why the flush must precede the read.
Most protocols take turns
The connection is full duplex, but almost no protocol uses it that way. The dominant shape is request/response: one side sends, the other answers, repeat until somebody quits. Your client is therefore always in one of two states — writing, or waiting for a reply — and the bug you are about to meet is what happens when the two ends disagree about which state they are in.
getOutputStream() gives you the write half, and you chain it exactly like the read half: a Writer with an explicit charset for a text protocol, DataOutputStream for a binary one, buffering underneath either.
Framing: what ends a request, what ends a response
A protocol has to pin down two boundaries, and they are usually different.
- What ends a request. In text protocols this is nearly always CRLF — the two bytes
\r\n. Not\n. Java'sprintlnwrites the platform line separator, so on Linux it writes\nand a strict server will simply keep waiting for the rest of your line. Write the terminator literally. - What ends a response. This varies. It may be a single line, a byte count given in a header, a terminator line, or end-of-stream. You must read the specification (or the telnet transcript) to find out.
Flush before you read — the deadlock you will write at least once
A BufferedWriter — and every Writer chained over a buffer — holds your bytes in memory until the buffer fills or you call flush(). If you write a command and go straight to readLine(), the command never left your process. The server has nothing to answer, so it does not answer; you block on the read; it blocks on its read. Two processes waiting for each other, forever.
This is a deadlock, not an exception. Nothing throws. Nothing logs. SO_TIMEOUT is what eventually turns it into a diagnosable SocketTimeoutException instead of a hang, which is the second reason to always set it. The rule is mechanical: every write that expects a reply is followed by a flush before the read.
new PrintWriter(writer, true) gives you auto-flush on println, which is convenient — but PrintWriter swallows every IOException and only reports trouble through checkError(). For protocol traffic you want to know the write failed, so prefer a plain Writer and an explicit flush().
Worked example — a dictionary client
The dict protocol (RFC 2229) runs on TCP port 2628 and is a genuine multi-turn conversation, which makes it the right model for the protocol you design in Module 9.
Telnet it and the turn structure is immediate. The server speaks first, with a 220 banner. You send DEFINE eng-lat gold and it answers 150 1 definitions retrieved, then a 151 line naming the dictionary, then the definition text, then a single . alone on a line, then 250 ok. Ask for a word it does not have and you get 552 no match and no body at all. Send QUIT and it answers 221 bye and closes.
Three structural facts drive the code: control lines start with a three-digit status code, the body is terminated by a lone ., and the client must both check the code and honour the terminator. That is four blocks of code — connect, read banner, command loop, quit — and it is the template A3's Poem-of-the-Day server has to satisfy from the other side.