Binding, options, and clean shutdown
◈ 5 cardsThe four ServerSocket constructors, why the no-arg one exists, the only three options server sockets have (SO_TIMEOUT, SO_REUSEADDR, SO_RCVBUF), and the isBound/isClosed pair that tells you whether it is really open.
Four constructors, three knobs
new ServerSocket(int port)
new ServerSocket(int port, int queueLength)
new ServerSocket(int port, int queueLength, InetAddress bindAddress)
new ServerSocket()
The first three all bind immediately and differ only in how much they let you specify. queueLength is the kernel accept queue from the last lesson — ask for more than the OS maximum and you silently get the maximum. bindAddress restricts the listener to one network interface: on a machine with a public address and a private one, binding only the private address makes the server unreachable from the internet, which is a cheap and effective access control.
Two special values pay for themselves. Port 0 means "any free port" — the system picks an ephemeral port and getLocalPort() tells you which, the mechanism behind FTP data connections and behind every integration test that must not collide with a developer's running server. And a null bind address (or the absent third argument) means every interface, which is what you almost always want and what the toString() shows as 0.0.0.0.
Construct now, bind later
The no-arg ServerSocket() creates an object that is not bound to anything. It cannot accept until you call bind(SocketAddress) or bind(SocketAddress, int queueLength). That looks like a pointless extra step until you need to set an option that is only honoured before binding — and two of the three are. The pattern is: construct, configure, bind.
ServerSocket listener = new ServerSocket();
listener.setReuseAddress(true);
listener.bind(new InetSocketAddress(1313), 100);
The only three options
A client Socket has eight options. A ServerSocket has three, plus a hint.
SO_TIMEOUT(setSoTimeout(int ms)) is how longaccept()waits before throwing aSocketTimeoutException. The default,0, means never. Most servers want exactly that — but a timeout of a second or two is the standard way to make an accept loop interruptible, since the woken thread can re-check arunningflag and then loop back. Set it before callingaccept(); you cannot change it whileaccept()is blocked.SO_REUSEADDR(setReuseAddress(boolean)) decides whether a new socket may bind a port that a recently closed connection is still lingering on. Without it, stopping and immediately restarting a server gives youBindException: Address already in usefor a minute or two, for a port nothing is actually using. The default is platform-dependent — set it explicitly, beforebind().SO_RCVBUF(setReceiveBufferSize(int)) sets the receive buffer that accepted sockets inherit, which is why it lives on the server socket at all: you cannot change an accepted socket's buffer after the fact. Anything above 64 KB must be set on an unbound socket, so a large receive buffer forces the no-arg constructor.
setPerformancePreferences(connectionTime, latency, bandwidth) ranks three qualities for accepted sockets — but it is only a hint to the TCP stack, many implementations ignore it entirely, and you should never depend on it.
isBound() and isClosed() are both needed
Neither method means what its name suggests on its own. isBound() asks "has this socket ever been bound?" — it stays true after close(), forever. And a fresh no-arg ServerSocket that has never bound anything reports isClosed() == false, because it has not been closed either. Only the conjunction is the question you meant:
boolean open = listener.isBound() && !listener.isClosed();
Worked example — a daytime server you can restart and stop
ServerSocket listener = new ServerSocket(); // unbound
listener.setReuseAddress(true); // must precede bind()
listener.setReceiveBufferSize(128 * 1024); // >64 KB: also before bind()
listener.bind(new InetSocketAddress(1313), 100);
listener.setSoTimeout(2000); // accept() wakes every 2 s
while (running) {
try {
pool.submit(new DaytimeHandler(listener.accept()));
} catch (SocketTimeoutException ex) {
// no client in two seconds — loop round and re-read `running`
}
}
listener.close();
That server survives Ctrl-C and an immediate relaunch, because SO_REUSEADDR was set before the bind, and it stops on demand within two seconds of running going false, because accept() no longer blocks indefinitely. running must be volatile — the accepting thread and whoever flips the flag are different threads.