Memra

Binding, options, and clean shutdown

◈ 5 cards

The 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 long accept() waits before throwing a SocketTimeoutException. 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 a running flag and then loop back. Set it before calling accept(); you cannot change it while accept() 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 you BindException: Address already in use for a minute or two, for a port nothing is actually using. The default is platform-dependent — set it explicitly, before bind().
  • 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.

optionsetterbefore bind?the symptomSO_TIMEOUTsetSoTimeout(ms)noaccept() blocksforeverSO_REUSEADDRsetReuseAddress(true)yesaddress already inuseSO_RCVBUFsetReceiveBufferSize(n)over 64 KBslow bulk transfers(hint)setPerformancePreferencesyesoften ignoredoutrightConstruct, configure, bind.
Three options and a hint — a much shorter list than the client Socket’s. The middle column is the one that bites: SO_REUSEADDR and a receive buffer over 64 KB are ignored unless the socket is still unbound, which is the entire reason the no-arg constructor exists.
NORMAL ~/memra/learn/comp-348/server-socket-binding-options utf-8 LF