Chapter 11. Sockets for Servers

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

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:

  1. A new ServerSocket is created on a particular port using a ServerSocket( ) constructor.

  2. 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.

  3. 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.

  4. The server and the client interact according to an agreed-upon protocol until it is time to close the connection.

  5. The server, the client, or both close the connection.

  6. 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:

The Constructors

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.

public ServerSocket(int port) throws IOException, BindException

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.

public ServerSocket(int port, int queueLength) throws IOException, BindException

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.

public ServerSocket(int port, int queueLength, InetAddress bindAddress) throws BindException, IOException

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.

Accepting and Closing Connections

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.

public Socket accept( ) throws IOException

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.

Note

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 IOExceptions 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.

public void close( ) throws IOException

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 get Methods

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.

public InetAddress getInetAddress( )

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) {
}

public int getLocalPort( )

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.

Socket Options

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).

public void setSoTimeout(int timeout) throws SocketException

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);
}

public int getSoTimeout( ) throws IOException

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." );
  }

}

The Object Methods

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.

public String toString( )

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.

Implementation

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.

public static synchronized void setSocketFactory (SocketImpl Factory fac) throws IOException

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.

Protected final void implAccept(Socket s) throws IOException

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 ).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.42.196