2007-03-01
An irritating limitation of listening sockets
Traditional sockets implementations have an irritating limitation, at
least for TCP sockets: it is impossible to gracefully and cleanly shut
down listening (server) sockets.
The problem is that kernels accept new TCP connections for listening
sockets without asking your server; accept()
doesn't actually accept
the connection, it just gives you the next one that the kernel has
already accepted on your behalf.
This means that to shut down cleanly,
you need a two-stage process: first you tell the kernel to stop accepting
new connections for you, and then you process all of the existing
accepted connections. If you simply dump everything, clients will see
abrupt disconnections of established connections, because from their
perspective the connection is complete; their connect()
finished,
and they can even have been sending data.
Unfortunately, there's no two-stage close interface for server sockets (at least, none that I can see):
- if you just
close()
the socket, you flush the pendingaccept()
queue. - you can't un-
listen()
by setting the backlog size to 0 or -1 or the like. shutdown()
is either the same asclose()
, does nothing, or produces very peculiar results, depending on the system and the exact arguments; in no case does it cause furtherconnect()
calls from clients to be refused.SO_ACCEPTCONN
is a read-only socket option.
(You can do this with Unix domain sockets, by removing the socket file itself without closing your server socket fd.)
Among other consequences, this means that protocols where the client connections and immediately starts sending data are dangerous; the client had better be prepared for the entire conversation to fail. The only way to make sure that the server is really there (instead of in the process of shutting down) is to wait for it to send you something, which may be why so many Internet protocols start with greeting banners from the server.
(As a pragmatic matter the server can lower the risk by not closing
things down until accept()
on a non-blocking socket returns an error,
but there's still a concurrency race, just a smaller one.)