The last chapter discussed sockets from the standpoint of clients:
programs that open a socket to a server that’s listening for
connections. However, client sockets themselves aren’t enough;
clients aren’t much use unless they can talk to a server, and
if you think about it, the sockets we discussed in the last chapter
aren’t sufficient for writing servers. To create a
Socket
, you need to know the Internet host to
which you want to connect. When you’re writing a server, you
don’t know in advance who will contact you, and even if you
did, you wouldn’t know when that host wanted to contact you. In
other words, servers are like receptionists who sit by the phone and
wait for incoming calls. They don’t know who will call or when,
only that when the phone rings, they have to pick it up and talk to
whoever is there. We can’t program that behavior with the
Socket
class alone. Granted, there’s no
reason that clients written in Java have to talk to Java
servers—in fact, a client doesn’t care what language the
server was written in or what platform it runs on. However, if Java
didn’t let us write servers, there would be a glaring hole in
its capabilities.
Fortunately, there’s no such hole.
Java provides a ServerSocket
class to allow
programmers to write servers. Basically, a server socket’s job
is to sit by the phone and wait for incoming calls. More technically,
a ServerSocket
runs on the server and listens for
incoming TCP connections. Each ServerSocket
listens on a particular port on the server machine. When a client
Socket
on a remote host attempts to connect to
that port, the server wakes up, negotiates the connection between the
client and the server, and opens a regular Socket
between the two hosts. In other words, server sockets wait for
connections while client sockets initiate connections. Once the
server socket has set up the connection, the server uses a regular
Socket
object to send data to the client. Data
always travels over the regular socket.
The
ServerSocket
class contains everything you need to
write servers in Java. It has constructors that create new
ServerSocket
objects, methods that listen for
connections on a specified port, and methods that return a
Socket
object when a connection is made so that
you can send and receive data. In addition, it has methods to set
various options and the usual miscellaneous methods such as
toString( )
.
The basic life cycle of a server is:
A new ServerSocket
is created on a particular port
using a ServerSocket( )
constructor.
The ServerSocket
listens for incoming connection
attempts on that port using its accept( )
method.
accept( )
blocks until a client attempts to make a
connection, at which point accept( )
returns a
Socket
object connecting the client and the
server.
Depending on the type of server, either the
Socket
’s getInputStream( )
method, getOutputStream( )
method, or
both are called to get input and output streams that communicate with
the client.
The server and the client interact according to an agreed-upon protocol until it is time to close the connection.
The server, the client, or both close the connection.
The server returns to step 2 and waits for the next connection.
If step 4 is likely to take a long or indefinite amount of time, traditional Unix servers such as wu-ftpd create a new process to handle each connection so that multiple clients can be serviced at the same time. Java programs should spawn a thread to interact with the client so that the server can be ready to process the next connection sooner. A thread places a far smaller load on the server than a complete child process. In fact, the overhead of forking too many processes is why the typical Unix FTP server can’t handle more than roughly 400 connections without slowing to a crawl. On the other hand, if the protocol is simple and quick and allows the server to close the connection when it’s through, then it will be more efficient for the server to process the client request immediately without spawning a thread.
The operating system stores incoming connection requests addressed to
a particular port in a first-in, first-out queue. The default length
of the queue is normally 50, though this can vary from operating
system to operating system. Some operating systems (though not
Solaris) have a maximum queue length, typically five. On these
systems, the queue length will be the largest possible value less
than or equal to 50. After the queue fills to capacity with
unprocessed connections, the host refuses additional connections on
that port until slots in the queue open up. Many (though not all)
clients will try to make a connection multiple times if their initial
attempt is refused. Managing incoming connections and the queue is a
service provided by the operating system; your program does not need
to worry about it. Several ServerSocket
constructors allow you to change the length of the queue if its
default length isn’t large enough; however, you won’t be
able to increase the queue beyond the maximum size that the operating
system supports:
There are three public
ServerSocket
constructors:
public ServerSocket(int port) throws IOException, BindException public ServerSocket(int port, int queueLength) throws IOException, BindException public ServerSocket(int port, int queueLength, InetAddress bindAddress) throws IOException
These constructors let you specify the port, the length of the queue used to hold incoming connection requests, and the local network interface to bind to. They pretty much all do the same thing, though some use default values for the queue length and the address to bind to. Let’s explore these in order.
This constructor creates a server socket on the port specified by the argument. If you pass for the port number, the system selects an available port for you. A port chosen for you by the system is sometimes called an anonymous port since you don’t know its number. For servers, anonymous ports aren’t very useful because clients need to know in advance which port to connect to; however, there are a few situations (which we will discuss later) in which an anonymous port might be useful.
For example, to create a server socket that would be used by an HTTP server on port 80, you would write:
try { ServerSocket httpd = new ServerSocket(80); } catch (IOException e) { System.err.println(e); }
The constructor throws an IOException
(specifically, a BindException
) if the socket
cannot be created and bound to the requested port. An
IOException
when creating a
ServerSocket
almost always means one of two
things. Either another server socket, possibly
from a completely different program, is already using the requested
port, or you’re trying to connect to a port from 1 to 1023 on
Unix without root (superuser) privileges.
You can use this constructor to write a variation on the
PortScanner
programs of the previous chapter.
Example 11.1 checks for ports on the local machine by
attempting to create ServerSocket
objects on them
and seeing on which ports that fails. If you’re using Unix and
are not running as root, this program works only for ports 1,024 and
above.
Example 11-1. Look for Local Ports
import java.net.*; import java.io.*; public class LocalPortScanner { public static void main(String[] args) { for (int port = 1; port <= 65535; port++) { try { // the next line will fail and drop into the catch block if // there is already a server running on the port ServerSocket server = new ServerSocket(port); } catch (IOException e) { System.out.println("There is a server on port " + port + "."); } // end try } // end for } }
Here’s the output I got when running
LocalPortScanner
on my NT workstation:
D:JAVAJNP2examples11>java LocalPortScanner
There is a server on port 135.
There is a server on port 1025.
There is a server on port 1026.
There is a server on port 1027.
There is a server on port 1028.
This constructor creates a ServerSocket
on the
specified port with a queue length of your choosing. If the machine
has multiple network interfaces or IP addresses, then it listens on
this port on all those interfaces and IP addresses. The
queueLength
argument sets the length of the queue
for incoming connection requests—that is, how many incoming
connections can be stored at one time before the host starts refusing
connections. Some operating systems have a maximum queue length,
typically five. If you try to expand the queue past that maximum
number, the maximum queue length is used instead. If you pass
for the port number, the system selects an available port.
For example, to create a server socket on port 5,776 that would hold up to 100 incoming connection requests in the queue, you would write:
try { ServerSocket httpd = new ServerSocket(5776, 100); } catch (IOException e) { System.err.println(e); }
The constructor throws an IOException
(specifically, a BindException
) if the socket
cannot be created and bound to the requested port. An
IOException
when creating a
ServerSocket
almost always means one of two
things. Either the specified port is already in use, or you do not
have root privileges on Unix and you’re trying to connect to a
port from 1 to
1,023.
This constructor, which is available only in Java 1.1 and later,
creates a ServerSocket
on the specified port with
the specified queue length. This ServerSocket
binds only to the specified local IP address. This constructor is
useful for servers that run on systems with several IP addresses (a
common practice at web server farms) because it allows you to choose
the address to which you’ll listen. That is, this
ServerSocket
listens only for incoming connections
on the specified address; it won’t listen for connections that
come in through the host’s other addresses. The other
constructors bind to all local IP addresses by default.
For example, metalab.unc.edu is a particular SPARCstation in North Carolina. It’s connected to the Internet with the IP address 152.2.254.81. The same SPARCstation is also called www.gigabit-ethernet.org, but with a different IP address (152.2.254.82). To create a server socket that listens on port 5,776 of metalab.unc.edu but not on port 5,776 of www.gigabit-ethernet.org, you would write:
try { ServerSocket httpd = new ServerSocket(5776, 10, InetAddress.getHostByName("metalab.unc.edu")); } catch (IOException e) { System.err.println(e); }
The constructor throws an IOException
(again,
really a BindException
) if the socket cannot be
created and bound to the requested port. A
BindException
when creating a
ServerSocket
almost always means one of two
things. Either the specified port is already in use, or you do not
have root privileges on Unix and you’re trying to connect to a
port from 1 to 1,023.
A ServerSocket
generally operates in a loop that repeatedly accepts connections.
Each pass through the loop invokes the accept( )
method. This returns a Socket
object representing
the connection between the remote client and the local server.
Interaction with the client takes place through this
Socket
object. When the transaction is finished,
the server should invoke the Socket
object’s
close( )
method and get ready to process the next
incoming connection. However, when the server needs to shut down and
not process any further incoming connections, you should invoke the
ServerSocket
object’s close( )
method.
When server setup is done and you’re ready to accept a
connection, call the ServerSocket
’s
accept( )
method. This method
“blocks”: it stops the flow of execution and waits until
a client connects. When a client does connect, the accept( )
method returns a Socket
object. You
use the streams returned by this Socket
’s
getInputStream( )
and getOutputStream( )
methods to communicate with the client. For example:
ServerSocket server = new ServerSocket(5776); while (true) { Socket connection = server.accept( ); OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream( )); out.write("You've connected to this server. Bye-bye now. "); connection.close( ); }
If you don’t want your program to halt while it waits for a
connection, put the call to accept( )
in a
separate thread.
When you add exception handling, the code becomes somewhat more
convoluted. It’s important to distinguish between exceptions
thrown by the ServerSocket
,
which should probably shut down the server and log an error
message, and exceptions thrown by a
Socket
, which should just close
that active connection. Exceptions thrown by the accept( )
method are an intermediate case that can go either way.
To do this, you’ll need to nest your try
blocks. Finally, most servers will want to make sure that all sockets
they accept are closed when they’re finished. Even if the
protocol specifies that clients are responsible for closing
connections, clients do not always strictly adhere to the protocol.
The call to close( )
also has to be wrapped in a
try
block that catches an
IOException
. However, if you do catch an
IOException
when closing the socket, ignore it. It
just means that the client closed the socket before the server could.
Here’s a slightly more realistic example:
try { ServerSocket server = new ServerSocket(5776); while (true) { Socket connection = server.accept( ); try { OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream( )); out.write("You've connected to this server. Bye-bye now. "); connection.close( ); } catch (IOException e) { // This tends to be a transitory error for this one connection; // e.g. the client broke the connection early. Consequently, // we don't want to break the loop or print an error message. // However, you might choose to log this exception in an error log. } finally { // Most servers will want to guarantee that sockets are closed // when complete. try { if (connection != null) connection.close( ); } catch (IOException e) {} } } catch (IOException e) { System.err.println(e); }
Example 11.2 implements a simple daytime server, as per RFC 867. Since this server just sends a single line of text in response to each connection, it processes each connection immediately. More complex servers should spawn a thread to handle each request. In this case, the overhead of spawning a thread would be greater than the time needed to process the request.
If you run this program on a Unix box, you need to run it as root in order to connect to port 13. If you don’t want to or can’t run it as root, change the port number to something above 1024, say 1313.
Example 11-2. A Daytime Server
import java.net.*; import java.io.*; import java.util.Date; public class DaytimeServer { public final static int DEFAULT_PORT = 13; public static void main(String[] args) { int port = DEFAULT_PORT; if (args.length > 0) { try { port = Integer.parseInt(args[0]); if (port < 0 || port >= 65536) { System.out.println("Port must between 0 and 65535"); return; } } catch (NumberFormatException e) { // use default port } } try { ServerSocket server = new ServerSocket(port); Socket connection = null; while (true) { try { connection = server.accept( ); OutputStreamWriter out = new OutputStreamWriter(connection.getOutputStream( )); Date now = new Date( ); out.write(now.toString( ) +" "); out.flush( ); connection.close( ); } catch (IOException e) {} finally { try { if (connection != null) connection.close( ); } catch (IOException e) {} } } // end while } // end try catch (IOException e) { System.err.println(e); } // end catch } // end main } // end DaytimeServer
Example 11.2 is straightforward. The first three
lines import the usual packages, java.io
and
java.net
, as well as
java.util.Date
so we can get the time. There is a
single public final static int
field (i.e., a
constant) in the class DEFAULT_PORT
, which is set
to the well-known port for a daytime server (port 13). The class has
a single method, main( )
, which does all the work.
If the port is specified on the command-line, then it’s read
from args[0]
. Otherwise, the default port is used.
The outer try
block traps any
IOException
s that may arise while the
ServerSocket
server is constructed on the daytime
port or when it accepts connections. The inner try
block watches for exceptions thrown while the connections are
accepted and processed. The accept( )
method is
called within an infinite loop to watch for new connections; like
many servers, this program never terminates but continues listening
until an exception is thrown or you stop it manually.[21]
When a client makes a connection, accept( )
returns a Socket
, which is stored in the local
variable connection
, and the program continues. We
call getOutputStream( )
to get the output stream
associated with that Socket
and then chain that
output stream to a new OutputStreamWriter
,
out
. To get the current date, we construct a new
Date
object and send it to the client by writing
its string representation on out
with
write( )
.
Finally, after the data is sent or an exception has been thrown, we
close connection
inside the
finally
block. Always close a socket when
you’re finished with it. In the previous chapter, we said that
a client shouldn’t rely on the other side of a connection to
close the socket. That goes triple for servers. Clients can time out
or crash; users can cancel transactions; networks can go down in
high-traffic periods. For any of these or a dozen more reasons, you
cannot rely on clients to close sockets, even when the protocol
requires them to (which it doesn’t in this case).
Sending binary, nontext data is not significantly harder. Example 11.3 demonstrates with a time server. This follows
the time protocol outlined in RFC 868. When a client connects, the
server sends a 4-byte, big-endian, unsigned integer specifying the
number of seconds that have passed since 12:00 A.M., January 1, 1900
GMT (the epoch). The current time can be retrieved simply by creating
a new Date
object. However, since the
Date
class counts milliseconds since 12:00 A.M.,
January 1, 1970 GMT rather than seconds since 12:00 A.M., January 1,
1900 GMT, some conversion is necessary.
Example 11-3. A Time Server
import java.net.*; import java.io.*; import java.util.Date; public class TimeServer { public final static int DEFAULT_PORT = 37; public static void main(String[] args) { int port = DEFAULT_PORT; if (args.length > 0) { try { port = Integer.parseInt(args[0]); if (port < 0 || port >= 65536) { System.out.println("Port must between 0 and 65535"); return; } } catch (NumberFormatException e) {} } // The time protocol sets the epoch at 1900, // the java Date class at 1970. This number // converts between them. long differenceBetweenEpochs = 2208988800L; try { ServerSocket server = new ServerSocket(port); while (true) { Socket connection = null; try { connection = server.accept( ); OutputStream out = connection.getOutputStream( ); Date now = new Date( ); long msSince1970 = now.getTime( ); long secondsSince1970 = msSince1970/1000; long secondsSince1900 = secondsSince1970 + differenceBetweenEpochs; byte[] time = new byte[4]; time[0] = (byte) ((secondsSince1900 & 0x00000000FF000000L) >> 24); time[1] = (byte) ((secondsSince1900 & 0x0000000000FF0000L) >> 16); time[2] = (byte) ((secondsSince1900 & 0x000000000000FF00L) >> 8); time[3] = (byte) (secondsSince1900 & 0x00000000000000FFL); out.write(time); out.flush( ); } // end try catch (IOException e) { } // end catch finally { if (connection != null) connection.close( ); } } // end while } // end try catch (IOException e) { System.err.println(e); } // end catch } // end main } // end TimeServer
As with the TimeClient
of the previous chapter,
most of the effort here goes into working with a data format (32-bit
unsigned integers) that Java doesn’t natively support.
If you’re finished with a server
socket, you should close it, especially if your program is going to
continue to run for some time. This frees up the port for other
programs that may wish to use it. Closing a
ServerSocket
should not be confused with closing a
Socket
. Closing a ServerSocket
frees a port on the local host, allowing another server to bind to
the port; closing a Socket
breaks the connection
between the local and the remote hosts.
Server sockets are closed automatically when a program dies, so
it’s not absolutely necessary to close them in programs that
terminate shortly after the ServerSocket
is no
longer needed. Nonetheless, it doesn’t hurt. For example, the
main loop of the LocalPortScanner
program might be
better written like this so that it doesn’t temporarily occupy
most of the ports on the system:
for (int port = 1; port <= 65535; port++) { try { // the next line will fail and drop into the catch block if // there is already a server running on the port ServerSocket server = new ServerSocket(port); server.close( ); } catch (IOException e) { System.out.println("There is a server on port " + port + "."); } // end try } // end for
The
ServerSocket
class provides two getter methods to
tell you the local address and port occupied by the server socket.
These are useful if you’ve opened a server socket on an
anonymous port and/or an unspecified network interface. This would be
the case, for one example, in the data connection of an FTP session.
This method returns the address being
used by the server (the local host). If the local host has a single
IP address (as most do), then this is the address returned by
InetAddress.getLocalHost( )
. If the local host has
more than one IP address, then the specific address returned is one
of the host’s IP addresses. You can’t predict which
address you will get. For example:
try { ServerSocket httpd = new ServerSocket(80); InetAddress ia = httpd.getInetAddress( ); } catch (IOException e) { }
The ServerSocket
constructors allow you to listen
on an unspecified port by passing
for the port number. This method lets you find out what port
you’re listening on. You might use this in a peer-to-peer
multisocket program where you already have a means to inform other
peers of your location. Or a server might spawn several smaller
servers to perform particular operations. The well-known server could
inform clients what ports they can find the smaller servers on. Of
course, you can also use getLocalPort( )
to find a
non-anonymous port, but why would you need to? Example 11.4 demonstrates.
Example 11-4. A Random Port
import java.net.*; import java.io.*; public class RandomPort { public static void main(String[] args) { try { ServerSocket server = new ServerSocket(0); System.out.println("This server runs on port " + server.getLocalPort( )); } catch (IOException e) { System.err.println(e); } } }
Here’s the output of several runs:
D:JAVAJNP2examples11>java RandomPort
This server runs on port 1154 D:JAVAJNP2examples11>java RandomPort
This server runs on port 1155 D:JAVAJNP2examples11>java RandomPort
This server runs on port 1156
At least on this VM, the ports aren’t really random; but they are at least indeterminate until runtime.
The
only socket option supported for server sockets is SO_TIMEOUT.
SO_TIMEOUT is the amount of time, in milliseconds, that
accept( )
waits for an incoming connection before
throwing a java.io.InterruptedIOException
. If
SO_TIMEOUT is 0, then accept( )
will never time
out. The default is to never time out.
Using SO_TIMEOUT is rather rare. You might need it if you were implementing a complicated and secure protocol that required multiple connections between the client and the server where some responses needed to occur within a fixed amount of time. Most servers are designed to run for indefinite periods of time and therefore use the default timeout value, which is (never time out).
The setSoTimeout( )
method sets the SO_TIMEOUT field for
this server socket object. The countdown starts when accept( )
is invoked. When the timeout expires, accept( )
throws an InterruptedIOException
. You
should set this option before calling accept( )
;
you cannot change the timeout value while accept( )
is waiting for a connection. The
timeout
argument must be greater than or equal to
zero; if it isn’t, the method throws an
IllegalArgumentException
. For example:
try { ServerSocket server = new ServerSocket(2048); server.setSoTimeout(30000); // block for no more than 30 seconds try { Socket s = server.accept( ); // handle the connection // ... } catch (InterruptedIOException e) { System.err.println("No connection within 30 seconds"); } finally { server.close( ); } catch (IOException e) { System.err.println("Unexpected IOException: " + e); }
The getSoTimeout( )
method returns this server
socket’s current SO_TIMEOUT value. For example:
public void printSoTimeout(ServerSocket server) { int timeout = server.getSoTimeOut( ); if (timeout > 0) { System.out.println(server + " will time out after " + timeout + "milliseconds."); } else if (timeout == 0) { System.out.println(server + " will never time out."); } else { System.out.println("Impossible condition occurred in " + server); System.out.println("Timeout cannot be less than zero." ); } }
jServerSocket
overrides only one of the standard
methods from java.lang.Object
, toString( )
. Thus, equality comparisons test for strict identity, and
server sockets are problematic in hash tables. Normally, this
isn’t a large problem.
A String
returned by
ServerSocket
’s toString( )
method looks like this:
ServerSocket[addr=0.0.0.0,port=0,localport=5776]
In current implementations, addr
is always 0.0.0.0
and port
is always 0. Presumably, these may become
something more interesting in the future. The
localport
is the local port on which the server is
listening for connections.
The ServerSocket
class provides two methods for
changing the default implementation of server sockets. I’ll
describe them only briefly here, since they’re primarily
intended for implementers of Java virtual machines rather than
application programmers.
This method sets the system’s server
SocketImplFactory
, which is the factory used to
create ServerSocket
objects. This is not the same
factory that is used to create client Socket
objects, though the syntax is similar; you can have one factory for
Socket
objects and a different factory for
ServerSocket
objects. You can set this factory
only once in a program, however. A second attempt to set the
SocketImplFactory
throws a
SocketException
.
Subclasses of ServerSocket
use this method to
implement accept( )
. You pass an unconnected
Socket
object to implAccept( )
.
(Doing this requires you to subclass Socket
as
well since the standard java.net.Socket
class
doesn’t provide a means to create unconnected sockets.) When
the method returns, the Socket
argument
s
is connected to a client.
[21] The command for stopping a program manually depends on your
system; under Unix, NT, and many other systems, CTRL-C will do the
job. If you are running the server in the background on a Unix
system, stop it by finding the server’s process ID and killing
it with the kill command (kill
pid
).
3.144.42.196