9.3 The Socket Class

     

The java.net.Socket class is Java's fundamental class for performing client-side TCP operations. Other client-oriented classes that make TCP network connections such as URL , URLConnection , Applet , and JEditorPane all ultimately end up invoking the methods of this class. This class itself uses native code to communicate with the local TCP stack of the host operating system. The methods of the Socket class set up and tear down connections and set various socket options. Because TCP sockets are more or less reliable connections, the interface that the Socket class provides to the programmer is streams . The actual reading and writing of data over the socket is accomplished via the familiar stream classes.

9.3.1 The Constructors

The nondeprecated public Socket constructors are simple. Each lets you specify the host and the port you want to connect to. Hosts may be specified as an InetAddress or a String . Ports are always specified as int values from 0 to 65,535. Two of the constructors also specify the local address and local port from which data will be sent. You might need to do this when you want to select one particular network interface from which to send data on a multihomed host.

The Socket class also has two protected constructors (one of which is now public in Java 1.4) that create unconnected sockets. These are useful when you want to set socket options before making the first connection.

9.3.1.1 public Socket(String host, int port) throws UnknownHostException, IOException

This constructor creates a TCP socket to the specified port on the specified host and attempts to connect to the remote host. For example:

 try {   Socket toOReilly = new Socket("www.oreilly.com", 80);   // send and receive data... } catch (UnknownHostException ex) {   System.err.println(ex); } catch (IOException ex) {   System.err.println(ex); } 

In this constructor, the host argument is just a hostname expressed as a String . If the domain name server cannot resolve the hostname or is not functioning, the constructor throws an UnknownHostException . If the socket cannot be opened for some other reason, the constructor throws an IOException . There are many reasons a connection attempt might fail: the host you're trying to reach may not be accepting connections, a dialup Internet connection may be down, or routing problems may be preventing your packets from reaching their destination.

Since this constructor doesn't just create a Socket object but also tries to connect the socket to the remote host, you can use the object to determine whether connections to a particular port are allowed, as in Example 9-1.

Example 9-1. Find out which of the first 1,024 ports seem to be hosting TCP servers on a specified host
 import java.net.*; import java.io.*; public class LowPortScanner {   public static void main(String[] args) {          String host = "localhost";     if (args.length > 0) {       host = args[0];     }     for (int i = 1; i < 1024; i++) {       try {         Socket s = new Socket(host, i);         System.out.println("There is a server on port " + i + " of "           + host);       }       catch (UnknownHostException ex) {         System.err.println(ex);         break;       }       catch (IOException ex) {         // must not be a server on this port       }     } // end for      }  // end main    }  // end PortScanner 

Here's the output this program produces on my local host. Your results will vary, depending on which ports are occupied. As a rule, more ports will be occupied on a Unix workstation than on a PC or a Mac:

 %  java LowPortScanner  There is a server on port 21 of localhost There is a server on port 22 of localhost There is a server on port 23 of localhost There is a server on port 25 of localhost There is a server on port 37 of localhost There is a server on port 111 of localhost There is a server on port 139 of localhost There is a server on port 210 of localhost There is a server on port 515 of localhost There is a server on port 873 of localhost 

If you're curious about what servers are running on these ports, try experimenting with Telnet. On a Unix system, you may be able to find out which services reside on which ports by looking in the file /etc/services . If LowPortScanner finds any ports that are running servers but are not listed in /etc/services , then that's interesting.

Although this program looks simple, it's not without its uses. The first step to securing a system is understanding it. This program helps you understand what your system is doing so you can find (and close) possible entrance points for attackers . You may also find rogue servers: for example, LowPortScanner might tell you that there's a server on port 800, which, on further investigation, turns out to be an HTTP server somebody is running to serve erotic GIFs, and which is saturating your T1. However, like most security tools, this program can be misused. Don't use LowPortScanner to probe a machine you do not own; most system administrators would consider that a hostile act.

9.3.1.2 public Socket(InetAddress host, int port) throws IOException

Like the previous constructor, this constructor creates a TCP socket to the specified port on the specified host and tries to connect. It differs by using an InetAddress object (discussed in Chapter 6) to specify the host rather than a hostname. It throws an IOException if it can't connect, but does not throw an UnknownHostException ; if the host is unknown, you will find out when you create the InetAddress object. For example:

 try {   InetAddress oreilly = InetAddress.getByName("www.oreilly.com");   Socket oreillySocket = new Socket(oreilly , 80);   // send and receive data... } catch (UnknownHostException ex) {   System.err.println(ex); } catch (IOException ex) {   System.err.println(ex); } 

In the rare case where you open many sockets to the same host, it is more efficient to convert the hostname to an InetAddress and then repeatedly use that InetAddress to create sockets. Example 9-2 uses this technique to improve on the efficiency of Example 9-1.

Example 9-2. Find out which of the ports at or above 1,024 seem to be hosting TCP servers
 import java.net.*; import java.io.*; public class HighPortScanner {   public static void main(String[] args) {          String host = "localhost";     if (args.length > 0) {       host = args[0];     }     try {       InetAddress theAddress = InetAddress.getByName(host);       for (int i = 1024; i < 65536; i++) {         try {           Socket theSocket = new Socket(theAddress, i);           System.out.println("There is a server on port "             + i + " of " + host);         }         catch (IOException ex) {           // must not be a server on this port         }       } // end for     } // end try     catch (UnknownHostException ex) {       System.err.println(ex);     }   }  // end main    }  // end HighPortScanner 

The results of this example are similar to the previous ones, except that HighPortScanner checks ports above 1,023.

9.3.1.3 public Socket(String host, int port, InetAddress interface, int localPort) throws IOException, UnknownHostException

This constructor creates a socket to the specified port on the specified host and tries to connect. It connects to the host and port specified in the first two arguments. It connects from the local network interface and port specified by the last two arguments. The network interface may be either physical (e.g., a different Ethernet card) or virtual (a multihomed host). If 0 is passed for the localPort argument, Java chooses a random available port between 1,024 and 65,535.

One situation where you might want to explicitly choose the local address would be on a router/firewall that uses dual Ethernet ports. Incoming connections would be accepted on one interface, processed , and forwarded to the local network from the other interface. Suppose you were writing a program to periodically dump error logs to a printer or send them over an internal mail server. You'd want to make sure you used the inward-facing network interface instead of the outward- facing network interface. For example,

 try {   InetAddress inward = InetAddress.getByName("router");   Socket socket = new Socket("mail", 25, inward, 0);   // work with the sockets... } catch (UnknownHostException ex) {   System.err.println(ex); } catch (IOException ex) {   System.err.println(ex); } 

By passing 0 for the local port number, I say that I don't care which port is used but I do want to use the network interface bound to the local hostname router.

This constructor can throw an IOException for all the usual reasons given in the previous constructors. Furthermore, an UnknownHostException will also be thrown if the remote host cannot be located.

Finally, an IOException (probably a BindException , although again that's just a subclass of IOException and not specifically declared in the throws clause of this method) will be thrown if the socket is unable to bind to the requested local network interface, which tends to limit the portability of applications that use this constructor. You could take deliberate advantage of this to restrict a compiled program to run on only a predetermined host. It would require customizing distributions for each computer and is certainly overkill for cheap products. Furthermore, Java programs are so easy to disassemble, decompile, and reverse engineer that this scheme is far from foolproof. Nonetheless, it might be part of a scheme to enforce a software license.

9.3.1.4 public Socket(InetAddress host, int port, InetAddress interface, int localPort) throws IOException

This constructor is identical to the previous one except that the host to connect to is passed as an InetAddress , not a String . It creates a TCP socket to the specified port on the specified host from the specified interface and local port, and tries to connect. If it fails, it throws an IOException . For example:

 try {   InetAddress inward = InetAddress.getByName("router");   InetAddress mail = InetAddress.getByName("mail");   Socket socket = new Socket(mail, 25, inward, 0);   // work with the sockets... } catch (UnknownHostException ex) {   System.err.println(ex); } catch (IOException ex) {   System.err.println(ex); } 

9.3.1.5 protected Socket( )

The Socket class also has two (three in Java 1.5) constructors that create an object without connecting the socket. You use these if you're subclassing Socket , perhaps to implement a special kind of socket that encrypts transactions or understands your local proxy server. Most of your implementation of a new socket class will be written in a SocketImpl object.

The noargs Socket() constructor installs the default SocketImpl (from either the factory or a java.net.PlainSocketImpl ). It creates a new Socket without connecting it, and is usually called by subclasses of java.net.Socket .

In Java 1.4, this constructor has been made public, and allows you to create a socket that is not yet connected to any host. You can connect later by passing a SocketAddress to one of the connect( ) methods. The most common reason to create a Socket object without connecting is to set socket options; many of these cannot be changed after the connection has been made. I'll discuss this soon.

9.3.1.6 protected Socket(SocketImpl impl)

This constructor installs the SocketImpl object impl when it creates the new Socket object. The Socket object is created but is not connected. This constructor is usually called by subclasses of java.net.Socket . You can pass null to this constructor if you don't need a SocketImpl . However, in this case, you must override all the base class methods that depend on the underlying SocketImpl . This might be necessary if you were using JNI to talk to something other than the default native TCP stack.

9.3.1.7 public Socket(Proxy proxy) // Java 1.5

Java 1.5 adds this constructor, which creates an unconnected socket that will use the specified proxy server. Normally, the proxy server a socket uses is controlled by the socksProxyHost and socksProxyPort system properties, and these properties apply to all sockets in the system. However a socket created by this constructor will use the specified proxy server instead. Most notably, you can pass Proxy.NO_PROXY for the argument to bypass all proxy servers completely and connect directly to the remote host. Of course, if a firewall prevents such connections, there's nothing Java can do about it, and the connection will fail.

If you want to use a particular proxy server, you can specify it by its address. For example, this code fragment uses the SOCKS proxy server at myproxy.example.com to connect to the host login. ibiblio .org :

 SocetAddress proxyAddress = new InetSocketAddress("myproxy.example.com", 1080); Proxy proxy = new Proxy(Proxy.Type.SOCKS,  proxyAddress) Socket s = new Socket(proxy); SocketAddress remote = new InetSocketAddress("login.ibiblio.org", 25); s.connect(remote); 

SOCKS is the only low-level proxy type Java understands. There's also a high-level Proxy.Type.HTTP that works in the application layer rather than the transport layer and a Proxy.Type.DIRECT that represents proxyless connections.

9.3.2 Getting Information About a Socket

To the programmer, Socket objects appear to have several private fields that are accessible through various getter methods. Actually, sockets have only one field, a SocketImpl ; the fields that appear to belong to the Socket actually reflect native code in the SocketImpl . This way, socket implementations can be changed without disturbing the programfor example, to support firewalls and proxy servers. The actual SocketImpl in use is almost completely transparent to the programmer.

9.3.2.1 public InetAddress getInetAddress( )

Given a Socket object, the getInetAddress( ) method tells you which remote host the Socket is connected to or, if the connection is now closed, which host the Socket was connected to when it was connected. For example:

 try {   Socket theSocket = new Socket("java.sun.com", 80);   InetAddress host = theSocket.getInetAddress( );   System.out.println("Connected to remote host " + host); }  // end try catch (UnknownHostException ex) {   System.err.println(ex); } catch (IOException ex) {   System.err.println(ex); } 

9.3.2.2 public int getPort( )

The getPort( ) method tells you which port the Socket is (or was or will be) connected to on the remote host. For example:

 try {   Socket theSocket = new Socket("java.sun.com", 80);   int port = theSocket.getPort( );   System.out.println("Connected on remote port " + port); }  // end try catch (UnknownHostException ex) {   System.err.println(ex); } catch (IOException ex) {   System.err.println(ex); } 

9.3.2.3 public int getLocalPort( )

There are two ends to a connection: the remote host and the local host. To find the port number for the local end of a connection, call getLocalPort( ) . For example:

 try {   Socket theSocket = new Socket("java.sun.com", 80, true);   int localPort = theSocket.getLocalPort( );   System.out.println("Connecting from local port " + localPort); }  // end try catch (UnknownHostException ex) {   System.err.println(ex); } catch (IOException ex) {   System.err.println(ex); } 

Unlike the remote port, which (for a client socket) is usually a "well-known port" that has been preassigned by a standards committee, the local port is usually chosen by the system at runtime from the available unused ports. This way, many different clients on a system can access the same service at the same time. The local port is embedded in outbound IP packets along with the local host's IP address, so the server can send data back to the right port on the client.

9.3.2.4 public InetAddress getLocalAddress( )

The getLocalAddress() method tells you which network interface a socket is bound to. You normally use this on a multihomed host, or one with multiple network interfaces. For example:

 try {   Socket theSocket = new Socket(hostname, 80);   InetAddress localAddress = theSocket.getLocalAddress( );   System.out.println("Connecting from local address  " + localAddress); }  // end try catch (UnknownHostException ex) {   System.err.println(ex); } catch (IOException ex) {   System.err.println(ex); } 

Example 9-3 reads a list of hostnames from the command-line, attempts to open a socket to each one, and then uses these four methods to print the remote host, the remote port, the local address, and the local port.

Example 9-3. Get a socket's information
 import java.net.*; import java.io.*; public class SocketInfo {   public static void main(String[] args) {     for (int i = 0; i < args.length; i++) {       try {         Socket theSocket = new Socket(args[i], 80);         System.out.println("Connected to " + theSocket.getInetAddress( )           + " on port "  + theSocket.getPort( ) + " from port "           + theSocket.getLocalPort( ) + " of "           + theSocket.getLocalAddress( ));       }  // end try       catch (UnknownHostException ex) {         System.err.println("I can't find " + args[i]);       }       catch (SocketException ex) {         System.err.println("Could not connect to " + args[i]);       }       catch (IOException ex) {         System.err.println(ex);       }     } // end for   }  // end main }  // end SocketInfo 

Here's the result of a sample run. I included www.oreilly.com on the command line twice in order to demonstrate that each connection was assigned a different local port, regardless of the remote host; the local port assigned to any connection is unpredictable and depends mostly on what other ports are in use. The connection to login.ibiblio.org failed because that machine does not run any servers on port 80:

 %  java SocketInfo www.oreilly.com www.oreilly.com www.macfaq.com    login.ibiblio.org  Connected to www.oreilly.com/208.201.239.37 on port 80 from port 49156 of  /192.168.254.25 Connected to www.oreilly.com/208.201.239.37 on port 80 from port 49157 of  /192.168.254.25 Connected to www.macfaq.com/216.254.106.198 on port 80 from port 49158 of  /192.168.254.25 Could not connect to login.ibiblio.org 

9.3.2.5 public InputStream getInputStream( ) throws IOException

The getInputStream( ) method returns an input stream that can read data from the socket into a program. You usually chain this InputStream to a filter stream or reader that offers more functionality DataInputStream or InputStreamReader , for examplebefore reading input. For performance reasons, it's also a very good idea to buffer the input by chaining it to a BufferedInputStream and/or a BufferedReader .

With an input stream, we can read data from a socket and start experimenting with some actual Internet protocols. One of the simplest protocols is called daytime, and is defined in RFC 867. There's almost nothing to it. The client opens a socket to port 13 on the daytime server. In response, the server sends the time in a human-readable format and closes the connection. You can test the daytime server with Telnet like this:

 %  telnet vision.poly.edu 13  Trying 128.238.42.35... Connected to vision.poly.edu. Escape character is '^]'. Wed Nov 12 23:39:15 2003 Connection closed by foreign host. 

The line "Wed Nov 12 23:39:15 2003" is sent by the daytime server. When you read the Socket 's InputStream , this is what you will get. The other lines are produced either by the Unix shell or by the Telnet program.

Example 9-4 uses the InputStream returned by getInputStream() to read the time sent by the daytime server.

Example 9-4. A daytime protocol client
 import java.net.*; import java.io.*; public class DaytimeClient {   public static void main(String[] args) {     String hostname;     if (args.length > 0) {       hostname = args[0];     }     else {       hostname = "time.nist.gov";     }     try {       Socket theSocket = new Socket(hostname, 13);       InputStream timeStream = theSocket.getInputStream( );       StringBuffer time = new StringBuffer( );       int c;       while ((c = timeStream.read( )) != -1) time.append((char) c);       String timeString = time.toString( ).trim( );         System.out.println("It is " + timeString + " at " + hostname);     }  // end try     catch (UnknownHostException ex) {       System.err.println(ex);     }     catch (IOException ex) {       System.err.println(ex);     }   }  // end main } // end DaytimeClient 

DaytimeClient reads the hostname of a daytime server from the command line and uses it to construct a new Socket that connects to port 13 on the server. If the hostname is omitted, the National Institute of Standards and Technology's time server at time.nist.gov is used. The client then calls theSocket.getInputStream( ) to get theSocket 's input stream, which is stored in the variable timeStream . Since the daytime protocol specifies ASCII, DaytimeClient doesn't bother chaining a reader to the stream. Instead, it just reads the bytes into a StringBuffer one at a time, breaking when the server closes the connection as the protocol requires it to do. Here's what happens:

 %  java DaytimeClient  It is 52956 03-11-13 04:45:28 00 0 0 706.3 UTC(NIST) * at time.nist.gov %  java DaytimeClient vision.poly.edu  It is Wed Nov 12 23:45:29 2003 at vision.poly.edu 

You can see that the clocks on time.nist.gov and vision.poly.edu aren't perfectly synchronized. Differences of a few seconds can be caused by the time it takes packets to travel across the Internet. For more details about network timekeeping, see http://www.boulder.nist.gov/timefreq/service/its.htm.

On top of that problem, the time servers on these two hosts use different formats. The daytime protocol doesn't specify the format for the time it returns, other than that it be human-readable. Therefore, it is difficult to convert the character data that the server returns to a Java Date in a reliable fashion. If you want to create a Date object based on the time at the server, it's easier to use the time protocol from RFC 868 instead, because it specifies a format for the time.

When reading data from the network, it's important to keep in mind that not all protocols use ASCII or even text. For example, the time protocol specified in RFC 868 specifies that the time be sent as the number of seconds since midnight, January 1, 1900 Greenwich Mean Time. However, this is not sent as an ASCII string like "2,524,521,600" or "-1297728000". Rather, it is sent as a 32-bit, unsigned, big-endian binary number.

The RFC never actually comes out and says that this is the format used. It specifies 32 bits and assumes you know that all network protocols use big-endian numbers . The fact that the number is unsigned can be determined only by calculating the wraparound date for signed and unsigned integers and comparing it to the date given in the specification (2036). To make matters worse , the specification gives an example of a negative time that can't actually be sent by time servers that follow the protocol. Time is a fairly old protocol, standardized in the early 1980s before the IETF was as careful about such issues as it is today. Nonetheless, if you find yourself implementing a not particularly well-specified protocol, you may have to do a significant amount of testing against existing implementations to figure out what you need to do. In the worst case, different existing implementations may behave differently.


Since this isn't text, you can't easily use Telnet to test such a service, and your program can't read the server response with a Reader or any sort of readLine() method. A Java program that connects to time servers must read the raw bytes and interpret them appropriately. In this example, that job is complicated by Java's lack of a 32-bit unsigned integer type. Consequently, you have to read the bytes one at a time and manually convert them into a long using the bitwise operators << and . Example 9-5 demonstrates . When speaking other protocols, you may encounter data formats even more alien to Java. For instance, a few network protocols use 64-bit fixed-point numbers. There's no shortcut to handle all possible cases. You simply have to grit your teeth and code the math you need to handle the data in whatever format the server sends.

Example 9-5. A time protocol client
 import java.net.*; import java.io.*; import java.util.*; public class TimeClient {            public final static int    DEFAULT_PORT = 37;   public final static String DEFAULT_HOST = "time.nist.gov";   public static void main(String[] args) {     String hostname = DEFAULT_HOST ;     int port = DEFAULT_PORT;          if (args.length > 0) {       hostname = args[0];     }     if (args.length > 1) {       try {          port = Integer.parseInt(args[1]);       }       catch (NumberFormatException ex) {         // Stay with the default port       }      }          // The time protocol sets the epoch at 1900,     // the Java Date class at 1970. This number      // converts between them.          long differenceBetweenEpochs = 2208988800L;          // If you'd rather not use the magic number, uncomment      // the following section which calculates it directly.          /*         TimeZone gmt = TimeZone.getTimeZone("GMT");     Calendar epoch1900 = Calendar.getInstance(gmt);     epoch1900.set(1900, 01, 01, 00, 00, 00);     long epoch1900ms = epoch1900.getTime( ).getTime( );     Calendar epoch1970 = Calendar.getInstance(gmt);     epoch1970.set(1970, 01, 01, 00, 00, 00);     long epoch1970ms = epoch1970.getTime( ).getTime( );          long differenceInMS = epoch1970ms - epoch1900ms;     long differenceBetweenEpochs = differenceInMS/1000;     */              InputStream raw = null;     try {       Socket theSocket = new Socket(hostname, port);       raw = theSocket.getInputStream( );              long secondsSince1900 = 0;       for (int i = 0; i < 4; i++) {         secondsSince1900 = (secondsSince1900 << 8)  raw.read( );       }       long secondsSince1970         = secondsSince1900 - differenceBetweenEpochs;              long msSince1970 = secondsSince1970 * 1000;       Date time = new Date(msSince1970);              System.out.println("It is " + time + " at " + hostname);        }  // end try     catch (UnknownHostException ex) {       System.err.println(ex);     }     catch (IOException ex) {       System.err.println(ex);     }     finally {       try {         if (raw != null) raw.close( );        }       catch (IOException ex) {}     }   }  // end main } // end TimeClient 

Here's the output of this program from a couple of sample runs. Since the time protocol specifies Greenwich Mean Time, the previous differences between time zones are eliminated. Most of the difference that's left simply reflects the clock drift between the two machines:

 %  java TimeClient  It is Wed Nov 12 23:49:15 EST 2003 at time.nist.gov %  java TimeClient vision.poly.edu  It is Wed Nov 12 23:49:20 EST 2003 at vision.poly.edu 

Like DaytimeClient , TimeClient reads the hostname of the server and an optional port from the command-line and uses it to construct a new Socket that connects to that server. If the user omits the hostname, TimeClient defaults to time.nist.gov . The default port is 37. The client then calls theSocket.getInputStream( ) to get an input stream, which is stored in the variable raw . Four bytes are read from this stream and used to construct a long that represents the value of those four bytes interpreted as a 32-bit unsigned integer. This gives the number of seconds that have elapsed since 12:00 A.M., January 1, 1900 GMT (the time protocol's epoch); 2,208,988,800 seconds are subtracted from this number to get the number of seconds since 12:00 A.M., January 1, 1970 GMT (the Java Date class epoch). This number is multiplied by 1,000 to convert it into milliseconds. Finally, that number of milliseconds is converted into a Date object, which can be printed to show the current time and date.

9.3.2.6 public OutputStream getOutputStream( ) throws IOException

The getOutputStream() method returns a raw OutputStream for writing data from your application to the other end of the socket. You usually chain this stream to a more convenient class like DataOutputStream or OutputStreamWriter before using it. For performance reasons, it's a good idea to buffer it as well. For example:

 Writer out; try {   Socket http = new Socket("www.oreilly.com", 80)   OutputStream raw = http.getOutputStream( );   OutputStream buffered = new BufferedOutputStream(raw);   out = new OutputStreamWriter(buffered, "ASCII");   out.write("GET / HTTP 1.0\r\n\r\n");   // read the server response... } catch (Exception ex) {   System.err.println(ex); } finally {   try {     out.close( );   }   catch (Exception ex) {}   } 

The echo protocol, defined in RFC 862, is one of the simplest interactive TCP services. The client opens a socket to port 7 on the echo server and sends data. The server sends the data back. This continues until the client closes the connection. The echo protocol is useful for testing the network to make sure that data is not mangled by a misbehaving router or firewall. You can test echo with Telnet like this:

 %  telnet rama.poly.edu 7  Trying 128.238.10.212... Connected to rama.poly.edu. Escape character is '^]'.  This is a test  This is a test  This is another test  This is another test  9876543210  9876543210  ^]  telnet>  close  Connection closed. 

Example 9-6 uses getOutputStream() and getInputStream( ) to implement a simple echo client. The user types input on the command line which is then sent to the server. The server echoes it back. The program exits when the user types a period on a line by itself. The echo protocol does not specify a character encoding. Indeed, what it specifies is that the data sent to the server is exactly the data returned by the server. The server echoes the raw bytes, not the characters they represent. Thus, this program uses the default character encoding and line separator of the client system for reading the input from System.in , sending the data to the remote system, and typing the output on System.out . Since an echo server echoes exactly what is sent, it's as if the server dynamically adjusts itself to the client system's conventions for character encoding and line breaks. Consequently, we can use convenient classes and methods such as PrintWriter and readLine( ) that would normally be too unreliable.

Example 9-6. An echo client
 import java.net.*; import java.io.*; public class EchoClient {   public static void main(String[] args) {     String hostname = "localhost";     if (args.length > 0) {       hostname = args[0];     }     PrintWriter out = null;     BufferedReader networkIn = null;     try {       Socket theSocket = new Socket(hostname, 7);       networkIn = new BufferedReader(        new InputStreamReader(theSocket.getInputStream( )));       BufferedReader userIn = new BufferedReader(        new InputStreamReader(System.in));       out = new PrintWriter(theSocket.getOutputStream( ));       System.out.println("Connected to echo server");       while (true) {         String theLine = userIn.readLine( );         if (theLine.equals(".")) break;         out.println(theLine);         out.flush( );         System.out.println(networkIn.readLine( ));       }            }  // end try     catch (IOException ex) {       System.err.println(ex);     }     finally {       try {         if (networkIn != null) networkIn.close( );          if (out != null) out.close( );        }       catch (IOException ex) {}     }   }  // end main }  // end EchoClient 

As usual, EchoClient reads the name of the host to connect to from the command line. This hostname is used to create a new Socket object on port 7, called theSocket . The socket's InputStream is returned by getInputStream( ) and chained to an InputStreamReader , which is chained to a BufferedReader called networkIn . This reader reads the server responses. Since this client also needs to read input from the user, it creates a second BufferedReader , this one called userIn , which reads from System.in . Next , EchoClient calls theSocket.getOutputStream( ) to get theSocket 's output stream, which is used to construct a new PrintWriter called out .

Now that the three streams have been created, it's simply a matter of reading the data from userIn and writing that data back out onto out . Once data has been sent to the echo server, networkIn waits for a response. When networkIn receives a response, it's printed on System.out . In theory, this client could get hung waiting for a response that never comes. However, this is unlikely if the connection can be made in the first place, since the TCP protocol checks for bad packets and automatically asks the server for replacements . When we implement a UDP echo client in Chapter 13, we will need a different approach because UDP does no error checking. Here's a sample run:

 %  java EchoClient rama.poly.edu  Connected to echo server  Hello  Hello  How are you?  How are you?  I'm fine thank you.  I'm fine thank you.  Goodbye  Goodbye  .  

Example 9-7 is line-oriented. It reads a line of input from the console, sends it to the server, and waits to read a line of output it gets back. However, the echo protocol doesn't require this. It echoes each byte as it receives it. It doesn't really care whether those bytes represent characters in some encoding or are divided into lines. Java does not allow you to put the console into "raw" mode, where each character is read as soon as it's typed instead of waiting for the user to press the Enter key. Consequently, if you want to explore the more immediate echo responses, you must provide a nonconsole interface. You also have to separate the network input from user input and network output. This is because the connection is full duplex but may be subject to some delay. If the Internet is running slow, the user may be able to type and send several characters before the server returns the first one. Then the server may return several bytes all at once. Unlike many protocols, echo does not specify lockstep behavior in which the client sends a request but then waits for the full server response before sending any more data. The simplest way to handle such a protocol in Java is to place network input and output in separate threads.

9.3.3 Closing the Socket

That's almost everything you need to know about client-side sockets. When you're writing a client application, almost all the work goes into handling the streams and interpreting the data. The sockets themselves are very easy to work with; all the hard parts are hidden. That is one reason sockets are such a popular paradigm for network programming. After we cover a couple of remaining methods, you'll know everything you need to know to write TCP clients.

9.3.3.1 public void close( ) throws IOException

Until now, the examples have assumed that sockets close on their own; they haven't done anything to clean up after themselves. It is true that a socket closes automatically when one of its two streams closes, when the program ends, or when it's garbage collected. However, it is a bad practice to assume that the system will close sockets for you, especially for programs that may run for an indefinite period of time. In a socket- intensive program like a web browser, the system may well hit its maximum number of open sockets before the garbage collector kicks in. The port scanner programs of Example 9-1 and Example 9-2 are particularly bad offenders in this respect, since it may take a long time for the program to run through all the ports. Shortly, you'll see a new version that doesn't have this problem.

When you're through with a socket, you should call its close( ) method to disconnect. Ideally, you put this in a finally block so that the socket is closed whether an exception is thrown or not. The syntax is straightforward:

 Socket connection = null;  try {   connection = new Socket("www.oreilly.com", 13);   // interact with the socket... }  // end try catch (UnknownHostException ex) {   System.err.println(ex); } catch (IOException ex) {   System.err.println(ex); } finally {  if (connection != null) connection.close( );  } 

Once a Socket has been closed, its InetAddress , port number, local address, and local port number are still accessible through the getInetAddress() , getPort( ) , getLocalAddress( ) , and getLocalPort( ) methods. However, although you can still call getInputStream( ) or getOutputStream( ) , attempting to read data from the InputStream or write data to the OutputStream throws an IOException .

Example 9-7 is a revision of the PortScanner program that closes each socket once it's through with it. It does not close sockets that fail to connect. Since these are never opened, they don't need to be closed. In fact, if the constructor failed, connection is actually null .

Example 9-7. Look for ports with socket closing
 import java.net.*; import java.io.*; public class PortScanner {   public static void main(String[] args) {          String host = "localhost";     if (args.length > 0) {       host = args[0];     }     try {       InetAddress theAddress = InetAddress.getByName(host);       for (int i = 1; i < 65536; i++) {         Socket connection = null;         try {           connection = new Socket(host, i);           System.out.println("There is a server on port "             + i + " of " + host);         }         catch (IOException ex) {           // must not be a server on this port         }         finally {           try {             if (connection != null) connection.close( );            }           catch (IOException ex) {}         }       } // end for     } // end try     catch (UnknownHostException ex) {       System.err.println(ex);     }   }  // end main    }  // end PortScanner 

Java 1.4 adds an isClosed() method that returns true is the socket has been closed, false if it isn't:

 public boolean isClosed( ) // Java 1.4 

If you're uncertain about a socket's state, you can check it with this method rather than risking an IOException . For example,

 if (socket.isClosed( )) {   // do something... } else {   // do something else... } 

However, this is not a perfect test. If the socket has never been connected in the first place, isClosed( ) returns false, even though the socket isn't exactly open.

Java 1.4 also adds an isConnected() method:

 public boolean isConnected( ) // Java 1.4 

The name is a little misleading. It does not tell you if the socket is currently connected to a remote host (that is, if it is unclosed). Instead it tells you whether the socket has ever been connected to a remote host. If the socket was able to connect to the remote host at all, then this method returns true, even after that socket has been closed. To tell if a socket is currently open, you need to check that isConnected( ) returns true and isClosed() returns false. For example:

 boolean connected = socket.isConnected( ) && ! socket.isClosed( ); 

Java 1.4 also adds an isBound() method:

 public boolean isBound( ) // Java 1.4 

Whereas isConnected( ) refers to the remote end of the socket, isBound( ) refers to the local end. It tells you whether the socket successfully bound to the outgoing port on the local system. This isn't very important in practice. It will become more important when we discuss server sockets in the next chapter.

9.3.3.2 Half-closed sockets // Java 1.3

The close( ) method shuts down both input and output from the socket. On occasion, you may want to shut down only half of the connection, either input or output. Starting in Java 1.3, the shutdownInput( ) and shutdownOutput() methods let you close only half of the connection:

 public void shutdownInput( ) throws IOException  // Java 1.3 public void shutdownOutput( ) throws IOException // Java 1.3 

This doesn't actually close the socket. However, it does adjust the stream connected to it so that it thinks it's at the end of the stream. Further reads from the input stream will return -1. Further writes to the output stream will throw an IOException .

Many protocols, such as finger, whois, and HTTP begin with the client sending a request to the server, then reading the response. It would be possible to shut down the output after the client has sent the request. For example, this code fragment sends a request to an HTTP server and then shuts down the output, since it won't need to write anything else over this socket:

 Socket connection = null;  try {   connection = new Socket("www.oreilly.com", 80);   Writer out = new OutputStreamWriter(    connection.getOutputStream( ), "8859_1");   out.write("GET / HTTP 1.0\r\n\r\n");   out.flush( );  connection.shutdownOutput( );  // read the response... } catch (IOException ex) { } finally {   try {     if (connection != null) connection.close( );    }    catch (IOException ex) {} } 

Notice that even though you shut down half or even both halves of a connection, you still need to close the socket when you're through with it. The shutdown methods simply affect the socket's streams. They don't release the resources associated with the socket such as the port it occupies.

Java 1.4 adds two methods that tell you whether the input and output streams are open or closed:

 public boolean isInputShutdown( )   // Java 1.4 public boolean isOutputShutdown( )  // Java 1.4 

You can use these (rather than isConnected( ) and isClosed( ) ) to more specifically ascertain whether you can read from or write to a socket.

9.3.4 Setting Socket Options

Socket options specify how the native sockets on which the Java Socket class relies send and receive data. You can set four options in Java 1.1, six in Java 1.2, seven in Java 1.3, and eight in Java 1.4:

  • TCP_NODELAY

  • SO_BINDADDR

  • SO_TIMEOUT

  • SO_LINGER

  • SO_SNDBUF (Java 1.2 and later)

  • SO_RCVBUF (Java 1.2 and later)

  • SO_KEEPALIVE (Java 1.3 and later)

  • OOBINLINE (Java 1.4 and later)

The funny -looking names for these options are taken from the named constants in the C header files used in Berkeley Unix where sockets were invented. Thus they follow classic Unix C naming conventions rather than the more legible Java naming conventions. For instance, SO_SNDBUF really means "Socket Option Send Buffer Size ."

9.3.4.1 TCP_NODELAY
 public void setTcpNoDelay(boolean on) throws SocketException public boolean getTcpNoDelay( ) throws SocketException 

Setting TCP_NODELAY to true ensures that packets are sent as quickly as possible regardless of their size. Normally, small (one-byte) packets are combined into larger packets before being sent. Before sending another packet, the local host waits to receive acknowledgment of the previous packet from the remote system. This is known as Nagle's algorithm . The problem with Nagle's algorithm is that if the remote system doesn't send acknowledgments back to the local system fast enough, applications that depend on the steady transfer of small bits of information may slow down. This issue is especially problematic for GUI programs such as games or network computer applications where the server needs to track client-side mouse movement in real time. On a really slow network, even simple typing can be too slow because of the constant buffering. Setting TCP_NODELAY to true defeats this buffering scheme, so that all packets are sent as soon as they're ready.

setTcpNoDelay(true) turns off buffering for the socket. setTcpNoDelay(false) turns it back on. getTcpNoDelay( ) returns true if buffering is off and false if buffering is on. For example, the following fragment turns off buffering (that is, it turns on TCP_NODELAY) for the socket s if it isn't already off:

 if (!s.getTcpNoDelay( )) s.setTcpNoDelay(true); 

These two methods are each declared to throw a SocketException . They will be thrown only if the underlying socket implementation doesn't support the TCP_ NODELAY option.

9.3.4.2 SO_LINGER
 public void setSoLinger(boolean on, int seconds) throws SocketException public int getSoLinger( ) throws SocketException 

The SO_LINGER option specifies what to do with datagrams that have not yet been sent when a socket is closed. By default, the close( ) method returns immediately; but the system still tries to send any remaining data. If the linger time is set to zero, any unsent packets are thrown away when the socket is closed. If the linger time is any positive value, the close( ) method blocks while waiting the specified number of seconds for the data to be sent and the acknowledgments to be received. When that number of seconds has passed, the socket is closed and any remaining data is not sent, acknowledgment or no.

These two methods each throw a SocketException if the underlying socket implementation does not support the SO_LINGER option. The setSoLinger() method can also throw an IllegalArgumentException if you try to set the linger time to a negative value. However, the getSoLinger( ) method may return -1 to indicate that this option is disabled, and as much time as is needed is taken to deliver the remaining data; for example, to set the linger timeout for the Socket s to four minutes, if it's not already set to some other value:

 if (s.getTcpSoLinger( ) == -1) s.setSoLinger(true, 240); 

The maximum linger time is 65,535 seconds. Times larger than that will be reduced to 65,535 seconds. Frankly, 65,535 seconds (more than 18 hours) is much longer than you actually want to wait. Generally, the platform default value is more appropriate.

9.3.4.3 SO_TIMEOUT
 public void setSoTimeout(int milliseconds)   throws SocketException publicint getSoTimeout( ) throws SocketException 

Normally when you try to read data from a socket, the read() call blocks as long as necessary to get enough bytes. By setting SO_TIMEOUT, you ensure that the call will not block for more than a fixed number of milliseconds. When the timeout expires , an InterruptedIOException is thrown, and you should be prepared to catch it. However, the socket is still connected. Although this read( ) call failed, you can try to read from the socket again. The next call may succeed.

Timeouts are given in milliseconds. Zero is interpreted as an infinite timeout; it is the default value. For example, to set the timeout value of the Socket object s to 3 minutes if it isn't already set, specify 180,000 milliseconds:

 if (s.getSoTimeout( ) == 0) s.setSoTimeout(180000); 

These two methods each throw a SocketException if the underlying socket implementation does not support the SO_TIMEOUT option. The setSoTimeout() method also throws an IllegalArgumentException if the specified timeout value is negative.

9.3.4.4 SO_RCVBUF

Most TCP stacks use buffers to improve network performance. Larger buffers tend to improve performance for reasonably fast (say, 10Mbps and up) connections while slower, dialup connections do better with smaller buffers. Generally, transfers of large, continuous blocks of data, which are common in file transfer protocols such as FTP and HTTP, benefit from large buffers, while the smaller transfers of interactive sessions, such as Telnet and many games, do not. Relatively old operating systems designed in the age of small files and slow networks, such as BSD 4.2, use 2-kilobyte buffers. Somewhat newer systems, such as SunOS 4.1.3, use larger 4-kilobyte buffers by default. Still newer systems, such as Solaris, use 8- or even 16-kilobyte buffers. Starting in Java 1.2, there are methods to get and set the suggested receive buffer size used for network input:

 public void setReceiveBufferSize(int size)// Java 1.2  throws SocketException, IllegalArgumentException public int getReceiveBufferSize( ) throws SocketException  // Java 1.2 

The getReceiveBufferSize() method returns the number of bytes in the buffer that can be used for input from this socket. It throws a SocketException if the underlying socket implementation does not recognize the SO_RCVBUF option. This might happen on a non-POSIX operating system.

The setReceiveBufferSize() method suggests a number of bytes to use for buffering output on this socket. However, the underlying implementation is free to ignore this suggestion. The setReceiveBufferSize( ) method throws an IllegalArgumentException if its argument is less than or equal to zero. Although it's declared to also throw SocketException , it probably won't in practice since a SocketException is thrown for the same reason as IllegalArgumentException and the check for the IllegalArgument Exception is made first.

9.3.4.5 SO_SNDBUF

Starting in Java 1.2, there are methods to get and set the suggested send buffer size used for network output:

 public void setSendBufferSize(int size)                // Java 1.2  throws SocketException, IllegalArgumentException public int getSendBufferSize( ) throws SocketException  // Java 1.2 

The getSendBufferSize() method returns the number of bytes in the buffer used for output on this socket. It throws a SocketException if the underlying socket implementation doesn't understand the SO_SNDBUF option.

The setSendBufferSize( ) method suggests a number of bytes to use for buffering output on this socket. However, again thegggg client is free to ignore this suggestion. The setSendBufferSize( ) method also throws a SocketException if the underlying socket implementation doesn't understand the SO_SNDBUF option. However, it throws an IllegalArgumentException if its argument is less than or equal to zero.

9.3.4.6 SO_KEEPALIVE

If SO_KEEPALIVE is turned on, the client will occasionally send a data packet over an idle connection (most commonly once every two hours), just to make sure the server hasn't crashed. If the server fails to respond to this packet, the client keeps trying for a little more than 11 minutes until it receives a response. If it doesn't receive a response within 12 minutes, the client closes the socket. Without SO_KEEPALIVE, an inactive client could live more or less forever without noticing that the server had crashed.

Java 1.3 adds methods to turn SO_KEEPALIVE on and off and to determine its current state:

 public void setKeepAlive(boolean on) throws SocketException // Java 1.3 public boolean getKeepAlive( ) throws SocketException // Java 1.3 

The default for SO_KEEPALIVE is false. This code fragment turns SO_KEEPALIVE off, if it's turned on:

 if (s.getKeepAlive( )) s.setKeepAlive(false); 

9.3.4.7 OOBINLINE // Java 1.4

TCP includes a feature that sends a single byte of "urgent" data. This data is sent immediately. Furthermore, the receiver is notified when the urgent data is received and may elect to process the urgent data before it processes any other data that has already been received.

Java 1.4 adds support for both sending and receiving such urgent data. The sending method is named, obviously enough, sendUrgentData( ) :

 public void sendUrgentData(int data) throws IOException  // Java 1.4 

This method sends the lowest order byte of its argument almost immediately. If necessary, any currently cached data is flushed first.

How the receiving end responds to urgent data is a little confused , and varies from one platform and API to the next. Some systems receive the urgent data separately from the regular data. However, the more common, more modern approach is to place the urgent data in the regular received data queue in its proper order, tell the application that urgent data is available, and let it hunt through the queue to find it.

By default, Java pretty much ignores urgent data received from a socket. However, if you want to receive urgent data inline with regular data, you need to set the OOBINLINE option to true using these methods:

 public void setOOBInline(boolean on) throws SocketException // Java 1.3 public boolean getOOBInline( ) throws SocketException // Java 1.3 

The default for OOBInline is false. This code fragment turns OOBInline on, if it's turned off:

 if (s.getOOBInline( )) s.setOOBInline(true); 

Once OOBInline is turned on, any urgent data that arrives will be placed on the socket's input stream to be read in the usual way. Java does not distinguish it from non-urgent data.

9.3.4.8 SO_REUSEADDR // Java 1.4

When a socket is closed, it may not immediately release the local address, especially if a connection was open when the socket was closed. It can sometimes wait for a small amount of time to make sure it receives any lingering packets that were addressed to the port that were still crossing the network when the socket was closed. The system won't do anything with any of the late packets it receives. It just wants to make sure they don't accidentally get fed into a new process that has bound to the same port.

This isn't a big problem on a random port, but it can be an issue if the socket has bound to a well-known port because it prevents any other socket from using that port in the meantime. If the SO_REUSEADDR is turned on (it's turned off by default), another socket is allowed to bind to the port even while data may be outstanding for the previous socket.

In Java this option is controlled by these two methods:

 public void setReuseAddress(boolean on) throws SocketException public boolean getReuseAddress( ) throws SocketException 

For this to work, setReuseAddress() must be called before the new socket binds to the port. This means the socket must be created in an unconnected state using the no-args constructor; then setReuseAddress(true) is called, and the socket is connected using the connect( ) method. Both the socket that was previously connected and the new socket reusing the old address must set SO_REUSEADDR to true for it to take effect.

9.3.5 Class of Service

In the last few years , a lot of thought has gone into deriving different classes of service for different types of data that may be transferred across the Internet. For instance, video needs relatively high bandwidth and low latency for good performance, whereas email can be passed over low-bandwidth connections and even held up for several hours without major harm. It might be wise to price the different classes of service differentially so that people won't ask for the highest class of service automatically. After all, if sending an overnight letter cost the same as sending a package via media mail, we'd all just use Fed Ex overnight, which would quickly become congested and overwhelmed. The Internet is no different.

Currently, four traffic classes have been defined for TCP data, although not all routers and native TCP stacks support them. These classes are low cost, high reliability, maximum throughput, and minimum delay. Furthermore, they can be combined. For instance, you can request the minimum delay available at low cost. These measure are all fuzzy and relative, not hard and fast guarantees of service.

Java lets you inspect and set the class of service for a socket using these two methods:

 public int getTrafficClass( ) throws SocketException public void setTrafficClass(int trafficClass) throws SocketException 

The traffic class is given as an int between 0 and 255. (Values outside this range cause IllegalArgumentException s.) This int is a combination of bit-flags. Specifically:

  • 0x02: Low cost

  • 0x04: High reliability

  • 0x08: Maximum throughput

  • 0x10: Minimum delay

The lowest order, ones bit must be zero. The other three high order bits are not yet used. For example, this code fragment requests a low cost connection:

 Socket s = new Socket("www.yahoo.com", 80); s.setTrafficClass(0x02); 

This code fragment requests a connection with maximum throughput and minimum delay:

 Socket s = new Socket("www.yahoo.com", 80); s.setTrafficClass(0x08  0x10); 

The underlying socket implementation is not required to respect any of these requests. They only provide a hint to the TCP stack about the desired policy. Many implementations ignore these values completely. If the TCP stack is unable to provide the requested class of service, it may but is not required to throw a SocketException .

Java does not provide any means to access pricing information for the different classes of service. Be aware that your ISP may charge you for faster or more reliable connections using these features. (If they make it available at all. This is all still pretty bleeding edge stuff.)

Java 1.5 adds a slightly different method to set preferences, the setPerformancePreferences( ) method:

 public void setPerformancePreferences(int connectionTime,                                                 int latency, int bandwidth) 

This method expresses the relative preferences given to connection time, latency, and bandwidth. For instance, if connectionTime is 2 and latency is 1 and bandwidth is 3, then maximum bandwidth is the most important characteristic, minimum latency is the least important, and connection time is in the middle. Exactly how any given VM implements this is implementation-dependent. Indeed, it may be a no-op in some implementations. The documentation even suggests using non-TCP/IP sockets, though it's not at all clear what that means.

9.3.6 The Object Methods

The Socket class overrides only one of the standard methods from java.lang.Object , toString( ) . Since sockets are transitory objects that typically last only as long as the connection they represent, there's not much need or purpose to storing them in hash tables or comparing them to each other. Therefore, Socket does not override equals( ) or hashCode( ) , and the semantics for these methods are those of the Object class. Two Socket objects are equal to each other if and only if they are the same socket.

9.3.6.1 public String toString( )

The toString( ) method produces a string that looks like this:

 Socket[addr=www.oreilly.com/198.112.208.11,port=80,localport=50055] 

This is ugly and useful primarily for debugging. Don't rely on this format; it may change in the future. All parts of this string are accessible directly through other methods (specifically getInetAddress( ) , getPort( ) , and getLocalPort( ) ).



Java Network Programming
Java Network Programming, Third Edition
ISBN: 0596007213
EAN: 2147483647
Year: 2003
Pages: 164

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net