Advanced Socket Programming


In the following sections, we cover advanced issues that arise in real-world programs. We first show you how to use timeouts and interrupts to deal with connection errors. We show how the "half-close" mechanism can simplify request protocol, and we finish with a section on Internet addresses.

Socket Timeouts

In real-life programs, you don't just want to read from a socket, because the read methods will block until data are available. If the host is unreachable, then your application waits for a long time and you are at the mercy of the underlying operating system to time out eventually.

Instead, you should decide what timeout value is reasonable for your particular application. Then, call the setSoTimeout method to set a timeout value (in milliseconds).

 Socket s = new Socket(. . .); s.setSoTimeout(10000); // time out after 10 seconds 

If the timeout value has been set for a socket, then all subsequent read and write operations throw a SocketTimeoutException when the timeout has been reached before the operation has completed its work. You can catch that exception and react to the timeout.


try
{
   Scanner in = new Scanner(s.getInputStream());
   String line = in.nextLine();
   . . .
}
catch (InterruptedIOException exception)
{
   react to timeout
}

There is one additional timeout issue that you need to address: The constructor

 Socket(String host, int port) 

can block indefinitely until an initial connection to the host is established.

As of JDK 1.4, you can overcome this problem by first constructing an unconnected socket and then connecting it with a timeout:

 Socket s = new Socket(); s.connect(new InetSocketAddress(host, port), timeout); 

Interruptible Sockets

When you connect to a socket, the current thread blocks until the connection has been established or a timeout has elapsed. Similarly, when you read or write data through a socket, the current thread blocks until the operation is successful or has timed out.

In interactive applications, you would like to give users an option to simply cancel a socket connection that does not appear to produce results. However, if a thread blocks on an unresponsive socket, you cannot unblock it by calling interrupt.

To interrupt a socket operation, you use a SocketChannel, a feature of the java.nio package. Open the SocketChannel like this:

 SocketChannel channel = SocketChannel.open(new InetSocketAddress(host, port)); 

A channel does not have associated streams. Instead, it has read and write methods that make use of Buffer objects. (See Volume 1, Chapter 12 for more information about NIO buffers.) These methods are declared in interfaces ReadableByteChannel and WritableByteChannel.

If you don't want to deal with buffers, you can use the Scanner class to read from a SocketChannel because Scanner has a constructor with a ReadableByteChannel parameter:

 Scanner in = new Scanner(channel); 

To turn a channel into an output stream, use the static Channels.newOutputStream method.

 OutputStream outStream = Channels.newOutputStream(channel); 

That's all you need to do. Whenever a thread is interrupted during an open, read, or write operation, the operation does not block but is terminated with an exception.

The program in Example 3-7 shows how a thread that reads from a server can be interrupted. The server sends a stream of random numbers to the client. However, if the Busy checkbox is checked, then the server pretends to be busy and doesn't send anything. In either case, you can click the Cancel button, and the connection is terminated (see Figure 3-11).

Example 3-7. InterruptibleSocketTest.java

[View full width]

   1. import java.awt.*;   2. import java.awt.event.*;   3. import java.util.*;   4. import java.net.*;   5. import java.io.*;   6. import java.nio.channels.*;   7. import javax.swing.*;   8.   9. /**  10.    This program shows how to interrupt a socket channel.  11. */  12. public class InterruptibleSocketTest  13. {  14.    public static void main(String[] args)  15.    {  16.       JFrame frame = new InterruptibleSocketFrame();  17.       frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);  18.       frame.setVisible(true);  19.    }  20. }  21.  22. class InterruptibleSocketFrame extends JFrame  23. {  24.    public InterruptibleSocketFrame()  25.    {  26.       setSize(WIDTH, HEIGHT);  27.       setTitle("InterruptibleSocketTest");  28.  29.       JPanel northPanel = new JPanel();  30.       add(northPanel, BorderLayout.NORTH);  31.  32.       messages = new JTextArea();  33.       add(new JScrollPane(messages));  34.  35.       busyBox = new JCheckBox("Busy");  36.       northPanel.add(busyBox);  37.  38.       startButton = new JButton("Start");  39.       northPanel.add(startButton);  40.       startButton.addActionListener(new  41.          ActionListener()  42.          {  43.             public void actionPerformed(ActionEvent event)  44.             {  45.                startButton.setEnabled(false);  46.                cancelButton.setEnabled(true);  47.                connectThread = new Thread(new  48.                   Runnable()  49.                   {  50.                      public void run()  51.                      {  52.                         connect();  53.                      }  54.                   });  55.                connectThread.start();  56.             }  57.          });  58.  59.       cancelButton = new JButton("Cancel");  60.       cancelButton.setEnabled(false);  61.       northPanel.add(cancelButton);  62.       cancelButton.addActionListener(new  63.          ActionListener()  64.          {  65.             public void actionPerformed(ActionEvent event)  66.             {  67.                connectThread.interrupt();  68.                startButton.setEnabled(true);  69.                cancelButton.setEnabled(false);  70.             }  71.          });  72.       server = new TestServer();  73.       new Thread(server).start();  74.    }  75.  76.    /**  77.       Connects to the test server.  78.    */  79.    public void connect()  80.    {  81.       try  82.       {  83.          SocketChannel channel = SocketChannel.open(new InetSocketAddress("localhost" , 8189));  84.          try  85.          {  86.             in = new Scanner(channel);  87.             while (true)  88.             {  89.                if (in.hasNextLine())  90.                {  91.                   String line = in.nextLine();  92.                   messages.append(line);  93.                   messages.append("\n");  94.                }  95.                else Thread.sleep(100);  96.             }  97.          }  98.          finally  99.          { 100.             channel.close(); 101.             messages.append("Socket closed\n"); 102.          } 103.       } 104.       catch (IOException e) 105.       { 106.          messages.append("\nInterruptibleSocketTest.connect: " + e); 107.       } 108.       catch (InterruptedException e) 109.       { 110.          messages.append("\nInterruptibleSocketTest.connect: " + e); 111.       } 112.    } 113. 114.    /** 115.       A multithreaded server that listens to port 8189 and sends random numbers to  the client. 116.    */ 117.    class TestServer implements Runnable 118.    { 119.       public void run() 120.       { 121.          try 122.          { 123.             int i = 1; 124.             ServerSocket s = new ServerSocket(8189); 125. 126.             while (true) 127.             { 128.                Socket incoming = s.accept(); 129.                Runnable r = new RandomNumberHandler(incoming); 130.                Thread t = new Thread(r); 131.                t.start(); 132.             } 133.          } 134.          catch (IOException e) 135.          { 136.             messages.append("\nTestServer.run: " + e); 137.          } 138.       } 139.    } 140. 141.    /** 142.       This class handles the client input for one server socket connection. 143.    */ 144.    class RandomNumberHandler implements Runnable 145.    { 146.       /** 147.          Constructs a handler. 148.          @param i the incoming socket 149.       */ 150.       public RandomNumberHandler(Socket i) 151.       { 152.          incoming = i; 153.       } 154. 155.       public void run() 156.       { 157.          try 158.          { 159.             OutputStream outStream = incoming.getOutputStream(); 160.             PrintWriter out = new PrintWriter(outStream, true /* autoFlush */); 161.             Random generator = new Random(); 162.             while (true) 163.             { 164.                if (!busyBox.isSelected()) out.println(generator.nextInt()); 165.                Thread.sleep(100); 166.             } 167.          } 168.          catch (IOException e) 169.          { 170.             messages.append("\nRandomNumberHandler.run: " + e); 171.          } 172.          catch (InterruptedException e) 173.          { 174.             messages.append("\nRandomNumberHandler.run: " + e); 175.          } 176.       } 177. 178.       private Socket incoming; 179.    } 180. 181.    private Scanner in; 182.    private PrintWriter out; 183.    private JButton startButton; 184.    private JButton cancelButton; 185.    private JCheckBox busyBox; 186.    private JTextArea messages; 187.    private TestServer server; 188.    private Thread connectThread; 189. 190.    public static final int WIDTH = 300; 191.    public static final int HEIGHT = 300; 192. } 

Figure 3-11. Interrupting a socket


Half-Close

When a client program sends a request to the server, the server needs to be able to determine when the end of the request occurs. For that reason, many Internet protocols (such as SMTP) are line oriented. Other protocols contain a header that specifies the size of the request data. Otherwise, indicating the end of the request data is harder than writing data to a file. With a file, you'd just close the file at the end of the data. However, if you close a socket, then you immediately disconnect from the server.

The half-close overcomes this problem. You can close the output stream of a socket, thereby indicating to the server the end of the request data, but keep the input stream open so that you can read the response.

The client side looks like this:

 Socket socket = new Socket(host, port); Scanner in = new Scanner(socket.getInputStream()); PrintWriter writer = new PrintWriter(socket.getOutputStream()); // send request data writer.print(. . .); writer.flush(); socket.shutdownOutput(); // now socket is half closed // read response data while (in.hasNextLine()) != null) { String line = in.nextLine(); . . . } socket.close(); 

The server side simply reads input until the end of the input stream is reached.

Of course, this protocol is only useful for one-shot services such as HTTP where the client connects, issues a request, catches the response, and then disconnects.

Internet Addresses

Usually, you don't have to worry too much about Internet addressesthe numerical host addresses that consist of four bytes (or, with IPv6, 16 bytes) such as 132.163.4.102. However, you can use the InetAddress class if you need to convert between host names and Internet addresses.

As of JDK 1.4, the java.net package supports IPv6 Internet addresses, provided the host operating system does.

The static getByName method returns an InetAddress object of a host. For example,

 InetAddress address = InetAddress.getByName("time-A.timefreq.bldrdoc.gov"); 

returns an InetAddress object that encapsulates the sequence of four bytes 132.163.4.104. You can access the bytes with the getAddress method.

 byte[] addressBytes = address.getAddress(); 

Some host names with a lot of traffic correspond to multiple Internet addresses, to facilitate load balancing. For example, at the time of this writing, the host name java.sun.com corresponds to three different Internet addresses. One of them is picked at random when the host is accessed. You can get all hosts with the getAllByName method.

 InetAddress[] addresses = InetAddress.getAllByName(host); 

Finally, you sometimes need the address of the local host. If you simply ask for the address of localhost, you always get the address 127.0.0.1, which isn't very useful. Instead, use the static getLocalHost method to get the address of your local host.

 InetAddress address = InetAddress.getLocalHost(); 

Example 3-8 is a simple program that prints the Internet address of your local host if you do not specify any command-line parameters, or all Internet addresses of another host if you specify the host name on the command line, such as

 java InetAddressTest java.sun.com 

Example 3-8. InetAddressTest.java
  1. import java.net.*;  2.  3. /**  4.    This program demonstrates the InetAddress class.  5.    Supply a host name as command-line argument, or run  6.    without command-line arguments to see the address of the  7.    local host.  8. */  9. public class InetAddressTest 10. { 11.    public static void main(String[] args) 12.    { 13.       try 14.       { 15.          if (args.length > 0) 16.          { 17.             String host = args[0]; 18.             InetAddress[] addresses = InetAddress.getAllByName(host); 19.             for (InetAddress a : addresses) 20.                System.out.println(a); 21.          } 22.          else 23.          { 24.             InetAddress localHostAddress = InetAddress.getLocalHost(); 25.             System.out.println(localHostAddress); 26.          } 27.       } 28.       catch (Exception e) 29.       { 30.          e.printStackTrace(); 31.       } 32.    } 33. } 

NOTE

In this book, we cover only the TCP (Transmission Control Protocol) networking protocol. TCP establishes a reliable connection between two computers. The Java platform also supports the so-called UDP (User Datagram Protocol) protocol, which can be used to send packets (also called datagrams) with much less overhead than that for TCP. The drawback is that the packets can be delivered in random order or even dropped altogether. It is up to the recipient to put the packets in order and to request retransmission of missing packets. UDP is most suited for applications in which missing packets can be tolerated, for example, in audio or video streams, or for continuous measurements.



 java.net.Socket 1.0 

  • Socket()

    creates a socket that has not yet been connected.

  • void connect(SocketAddress address) 1.4

    connects this socket to the given address.

  • void connect(SocketAddress address, int timeoutInMilliseconds) 1.4

    connects this socket to the given address or returns if the time interval expired.

  • boolean isConnected() 1.4

    returns true if the socket is connected.

  • boolean isClosed() 1.4

    returns true if the socket is closed.

  • void setSoTimeout(int timeoutInMilliseconds) 1.1

    sets the blocking time for read requests on this socket. If the timeout is reached, then an InterruptedIOException is raised.

  • void shutdownOutput() 1.3

    sets the output stream to "end of stream."

  • void shutdownInput() 1.3

    sets the input stream to "end of stream."

  • boolean isOutputShutdown() 1.4

    returns true if output has been shut down.

  • boolean isInputShutdown() 1.4

    returns true if input has been shut down.


 java.net.InetAddress 1.0 

  • static InetAddress getByName(String host)

  • static InetAddress[] getAllByName(String host)

    construct an InetAddress, or an array of all Internet addresses, for the given host name.

  • static InetAddress getLocalHost()

    constructs an InetAddress for the local host.

  • byte[] getAddress()

    returns an array of bytes that contains the numerical address.

  • String getHostAddress()

    returns a string with decimal numbers, separated by periods, for example, "132.163.4.102".

  • String getHostName()

    returns the host name.


 java.net.InetSocketAddress 1.4 

  • InetSocketAddress(String hostname, int port)

    constructs an address object with the given host and port, resolving the host name during construction. If the host name cannot be resolved, then the address object's unresolved property is set to true.

  • boolean isUnresolved()

    returns true if this address object could not be resolved.


 java.nio.channels.SocketChannel 1.4 

  • static SocketChannel open(SocketAddress address)

    opens a socket channel and connects it to a remote address.


 java.nio.channels.Channels 1.4 

  • static InputStream newInputStream(ReadableByteChannel channel)

    constructs an input stream that reads from the given channel.

  • static OutputStream newOutputStream(WritableByteChannel channel)

    constructs an output stream that writes to the given channel.



    Core JavaT 2 Volume II - Advanced Features
    Building an On Demand Computing Environment with IBM: How to Optimize Your Current Infrastructure for Today and Tomorrow (MaxFacts Guidebook series)
    ISBN: 193164411X
    EAN: 2147483647
    Year: 2003
    Pages: 156
    Authors: Jim Hoskins

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