|
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 TimeoutsIn 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.
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 SocketsWhen 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 socketHalf-CloseWhen 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 AddressesUsually, 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.java1. 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
java.net.Socket 1.0
java.net.InetAddress 1.0
java.net.InetSocketAddress 1.4
java.nio.channels.SocketChannel 1.4
java.nio.channels.Channels 1.4
|
|