TCP Servers in Java
Recall that there is a fundamental asymmetry in the way hosts
initiate a TCP connection: servers sit around waiting for
for) connection requests, clients reach out
and make the connection requests.
We saw last class how to write a basic TCP client in Java. In
this lesson, we'll look at how to write a basic server.
The way the Java networking API is designed, the socket a
server uses to listen for connection request gets its own
ServerSocket. Normally you pass the
ServerSocket socket constructor the port number you want to
listen on as an argument, like this:
ServerSocket ssock = new ServerSocket(80);
You then make a blocking call that waits around for a
connection request to come in:
Socket sock = ssock.accept();
We're waiting a connection request from a client to come.
When it does, a new socket
is created, which
represents the server's side of the newly established
connection between server and client. The input stream for
sock goes to the newly connected client, the output side reads
data sent over the connection by the client.
A Simple "Time Server"
The following example shows a simple server that prints the time
to the process that connects to it.
A Better Simple "Time Server"
A big limitation of the previous example is that only one client
gets to interact with the server - then the server exits. We'd
have to restart it to server another client. This is almost
never how we want/expect a server to operate. Instead, we
expect it to go on serving clients indefinitely.
In other words, for most servers, the call to "accept" is
usually wrapped up inside an infinite while-loop. That turns
our simple time server from a one-off "tell the lucky first
caller the time", to a real service.
A Technically Better Simple "Time Server"
The usual way to deal with servers is to have the server spawn a
separate thread to deal with each connection. This way, while
the newly spawned thread is doing whatever it takes to provide
the service to the client, the server can accept more connections.
Now, the call to determine the time in our previous example is
likely to be pretty fast, so this probably isn't a real
concern. But most of the time it very definitely is, so here's
how it would typically be done.
A Netcat Server
We'll finish up by looking at a more realistic example of a
server. We'll provide a "netcat server", with the caveat that
our server will provide its service indefinitely to as many
clients as care to connect (which is not what nectat actually
does). Because netcat takes input from stdin on the server and
sends it to the client, we'll only accept connections one at a
time. That's not very typical of servers. In this case,
multiple active connections wouldn't make much sense. We still
have to deal with multiple threads, however, since we have to
read simultaneously from stdin and from our socket.