TCP Servers in Java

Recall that there is a fundamental asymmetry in the way hosts initiate a TCP connection: servers sit around waiting for (listening 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 class: 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 sock 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.