Section 8.4
Networking
AS FAR AS A PROGRAM IS CONCERNED, A NETWORK is just another possible source of input data, and another place where data can be output. That does oversimplify things, because networks are still not quite as easy to work with as files are. But in Java, you can do network communication using input streams and output streams, just as you can use such streams to communicate with the user or to work with files. Opening a network connection between two computers is a bit tricky, since there are two computers involved and they have to somehow agree to open a connection. And when each computer can send data to the other, synchronizing communication can be a problem. But the fundamentals are the same as for other forms of I/O.
One of the standard Java packages is called java.net. This package includes several classes that can be used for networking. Two different styles of network I/O are supported. One of these, which is fairly high-level, is based on the World-Wide Web, and provides the sort of network communication capability that is used by a Web browser when it downloads pages for you to view. The main class for this style of networking is called URL. An object of type URL is an abstract representation of a Universal Resource Locator, which is an address for an HTML document or other resource on the Web.
The second style of I/O is much more low-level, and is based on the idea of a socket. A socket is used by a program to establish a connection with another program on a network. Two-way communication over a network involves two sockets, one on each of the computers involved in the communication. Java uses a class called Socket to represent sockets that are used for network communication. The term "socket" presumably comes from an image of physically plugging a wire into a computer to establish a connection to a network, but it is important to understand that a socket, as the term is used here, is simply an object belonging to the class Socket. In particular, a program can have several sockets at the same time, each connecting it to another program running on some other computer on the network. All these connections use the same physical network connection.
This section gives a brief introduction to the URL and Socket classes, and shows how they relate to input and output streams and to exceptions.
The URL Class
The URL class is used to represent resources on the World-Wide Web. Every resource has an address, which identifies it uniquely and contains enough information for a Web browser to find the resource on the network and retrieve it. The address is called a "url" or "universal resource locator." See Section 5.3 for more information.
An object belonging to the URL class represents such an address. If you have a URL object, you can use it to open a network connection to the resource at that address. The URL class, and an associated class called URLConnection, provide a large number of methods for working with such connections. One of the methods in the URL class, getContent(), allows you retrieve the resource that the URL points to with a single command. That is, if url is an object of type URL, then url.getContent() is an Object that contains the text file, image, or other resource found on the Web at the given url. For example, if the resource is a standard Web page in HTML format, then the Object returned by getContent() is a String that contains the actual HTML code that describes the page. (At least, this is what should happen, according to the documentation as I read it, but I have found that instead of returning the String, the getContent() method might just return an InputStream from which you can read the string.)
A url is ordinarily specified as a string, such as "http://math.hws.edu/eck/index.html". There are also relative url's. A relative url specifies the location of a resource relative to the location of another url, which is called the base or context for the relative url. For example, if the context is given by the url http://math.hws.edu/eck/, then the incomplete, relative url "index.html" would really refer to http://math.hws.edu/eck/index.html.
An object of the class URL is not simply a string, but it can be constructed from a string representation of a url. A URL object can also be constructed from another URL object, representing a context, and a string that specifies a url relative to that context. These constructors have prototypes
public URL(String urlName) throws MalformedURLException and public URL(URL context, String relativeName) throws MalformedURLExceptionNote that these constructors will throw an exception of type MalformedURLException if the specified strings don't represent legal url's. So of course it's a good idea to put your call to the constructor inside a try statement and handle the potential MalformedURLException in a catch clause.
When you write an applet, there are two methods available that provide useful URL contexts. The method getDocumentBase(), defined in the Applet class, returns an object of type URL. This URL represents the location from which the HTML page that contains the applet was downloaded. This allows the applet to go back and retrieve other files that are stored in the same location as that document. For example,
URL address = new URL(getDocumentBase(), "data.txt");constructs a URL that refers to a file named data.txt on the same computer and in the same directory as the web page in which the applet is running. Another method, getCodeBase() returns a URL that gives the location of the applet itself (which is not necessarily the same as the location of the document).
Once you have a valid URL object, you can use the getContent() method to retrieve the data stored at that url. Alternatively, you can use the method openStream() from the URL class to obtain an InputStream from which you can read the data using any available input method. For example, if address is an object of type URL, you could simply say
InputStream in = address.openStream();to get the input stream. This method does all the work of opening a network connection. When you read from the input stream, it does all the hard work of obtaining data over that connection. Ordinarily, you would wrap the InputStream object in a DataInputStream or TextReader and do all your input through that.
Various exceptions can be thrown as the attempt is made to open the connection and read data from it. Most of these exception are of type IOException, and such errors must be caught and handled. But these operations can also cause security exceptions. An object of type SecurityException is thrown when a program attempts to perform some operation that it does not have permission to perform. For example, a Web browser is typically configured to forbid an applet from making a network connection to any computer other than the computer from which the applet was downloaded. If an applet attempts to connect to some other computer, a SecurityException is thrown. A security exception can be caught and handled like any other exception.
To put this all together, here is a routine that I use in an applet to read data from a url over the network. The url's getContent() method is used to retrieve the data. If the data is of type String, then the string is displayed in a TextArea. If getContent() returns an input stream, then the data is read from that stream and displayed. If getContent() returns any other type of object, then just the name of the class to which the object belongs is displayed. The data is read by a separate thread. Here is the run() method that does the work. Some of this will look unfamiliar to you, but you can probably figure it out:
public void run() { // Loads the data in the url specified by urlName into the text display. // Exception handling is used to detect and respond to errors that // might occur. try { URL url = new URL(getDocumentBase(), urlName); // Create a URL object. // (Can throw MalformedURLException) Object content = url.getContent(); // Read data from the URL. Can throw // SecurityException and MalformedURLExceprion if (content instanceof String) { textDisplay.setText((String)content); // set text area to show the data // read from the URL } else if (content instanceof InputStream || content instanceof Reader) { // I don't think this should happen, but it does on my Macintosh BufferedReader in; // for reading from the URL stream if (content instanceof InputStream) in = new BufferedReader(new InputStreamReader( (InputStream)content )); else if (content instanceof BufferedReader) in = (BufferedReader)content; else in = new BufferedReader( (Reader)content ); char[] data = new char[10000]; // buffer into which characters are read int index = 0; // number of chars already read while (index < 10000) { // try to read up to 10000 characters int charCt = in.read(data,index,10000-index); if (charCt == -1) break; index += charCt; } if (index == 0) textDisplay.setText("Couldn't get any data from the input stream."); else textDisplay.setText(new String(data,0,index)); in.close(); } else { textDisplay.setText("Loaded data of type\n " + content.getClass().getName()); // set text area to show what type of non-string data was read } } catch (MalformedURLException e) { // can be thrown when URL is created textDisplay.setText("\nERROR! Improper syntax given for the URL to be loaded."); } catch (SecurityException e) { // can be thrown when the connection is created textDisplay.setText("\nSECURITY ERROR! Can't access that URL."); } catch (IOException e) { // can be thrown while data is being read textDisplay.setText("\nINPUT ERROR! Problem reading data from that URL"); } finally { // This part is done, no matter what, before the thread ends loadButton.setEnabled(true); inputBox.setEditable(true); inputBox.selectAll(); inputBox.requestFocus(); } } // end of run() methodAnd here is the working applet that uses this routine. When you press the "Load" button, the applet will try to load data from the url specified in the text-input box. When the applet first starts up, it contains a url for its own source code. If you click the Load button, it will try to load and display that source code. (If there is a problem, and if you would still like to see the complete source code, you can find it in the file URLExampleApplet.java.)
You can also try to use this applet to look at the HTML source code for this very page. Just type s4.html into the input box at the bottom of the applet and then click on the Load button. You might want to experiment with generating other urls and seeing what types of errors can occur. For example, entering "bogus.html" is likely to generate a FileNotFoundException, since no document of that name exists in the directory that contains this page. As another example, you can probably generate a SecurityException by trying to connect to http://www.whitehouse.gov. (Not because it's an official secret -- any url that does not lead back to the same computer from which the applet was loaded will generate a security exception.)
Sockets, Clients, and Servers
Communication over the Internet is based on a pair of protocols called the Internet Protocol and the Transmission Control Protocol, which are collectively referred to as TCP/IP. (In fact, there is a basic type of communication that can be done without TCP, but for this discussion, I'll stick to the full TCP/IP, which provides reliable two-way communication between networked computers.)
For two programs to communicate using TCP/IP, each program must create a socket, as discussed earlier in this section, and those sockets must be connected. Once such a connection is made, communication takes place using input streams and output streams. Each program has its own input stream and its own output stream. Data written by one program to its output stream is transmitted to the other computer. There, it enters the input stream of the program at the other end of the network connection. When that program reads data from its input stream, it is receiving the data that was transmitted to it over the network.
The hard part, then, is making a network connection in the first place. Two sockets are involved. To get things started, one program must create a socket that will wait passively until a connection request comes in from another socket. The waiting socket is said to be listening for a connection. On the other side of the connection-to-be, another program creates a socket that sends out a connection request to the listening socket. When the listening socket receives the connection request, it responds, and the connection is established. Once that is done, each program can obtain an input stream and an output stream for the connection. Communication takes place through these streams until one program or the other closes the connection.
A program that creates a listening socket is sometimes said to be a server, and the socket is called a server socket. A program that connects to a server is called a client, and the socket that it used to make a connection is called a client socket. The idea is that the server is out there somewhere on the network, waiting for a connection request from some client. The server can be thought of as offering some kind of service, and the client gets access to that service by connecting to the server. This is called the client/server model of network communication. In many actual applications, a server program can provide connections to several clients at the same time. When a client connects to a server's listening socket, that socket does not stop listening. Instead, it continues listening for additional client connections at the same time that the first client is being serviced.
This client/server model, in which there is one server program that supports multiple clients, is a perfect application for threads. A server program has one main thread that manages the listening socket. This thread runs continuously as long as the server is in operation. Whenever the server socket receives a connection request from a client, the main thread makes a new thread to handle the communications with that particular client. This client thread will run only as long as the client stays connected. The server thread and any active client threads all run simultaneously, in parallel. Client programs, on the other hand, tend to be simpler, having just one network connection and just one thread (although there is nothing to stop a program from using several client sockets at the same time, or even a mixture of client sockets and server sockets).
The URL class that was discussed at the beginning of this section uses a client socket behind the scenes to do any necessary network communication. On the other side of that connection is a server program that accepts a connection request from the URL object, reads a request from that object for some particular file on the server computer, and responds by transmitting the contents of that file over the network back to the URL object. After transmitting the data, the server closes the connection.
To implement TCP/IP connections, the java.net package provides two classes, ServerSocket and Socket. A ServerSocket represents a listening socket that waits for connection requests from clients. A Socket represents one endpoint of an actual network connection. A Socket, then, can be a client socket that sends a connection request to a server. But a Socket can also be created by a server to handle a connection request from a client. This allows the server to create multiple sockets and handle multiple connections. (A ServerSocket does not itself participate in connections; it just listens for connection requests and creates Sockets to handle the actual connections.)
To use Sockets and ServerSockets, you need to know about internet addresses. After all, a client program has to have some way to specify which computer, among all those on the network, it wants to communicate with. Every computer on the Internet has an IP address which identifies it uniquely among all the computers on the net. Many computers can also be referred to by domain names such as math.hws.edu or www.whitehouse.gov. (See Section 1.7.) Now, a single computer might have several programs doing network communication at the same time, or one program communicating with several other computers. To allow for this possibility, a port number is added to the Internet address. A port number is just a 16-bit integer. A server does not simply listen for connections -- it listens for connections on a particular port. A potential client must know both the Internet address of the computer on which the server is running and the port number on which the server is listening. A Web server, for example, generally listens for connections on port 80; other standard Internet services also have standard port numbers. (The standard port numbers are all less than 1024. If you create your own server programs, you should use port numbers greater than 1024.)
When you construct a ServerSocket object, you have to specify the port number on which the server will listen. The prototype for the constructor is
public ServerSocket(int port) throws IOExceptionAs soon as the ServerSocket is established, it starts listening for connection requests. The accept() method in the ServerSocket class accepts such a request, establishes a connection with the client, and returns a Socket that can be used for communication with the client. The accept() method has the form
public Socket accept() throws IOExceptionWhen you call the accept() method, it will not return until a connection request is received (or until some error occurs). The method is said to block while waiting for the connection. While the method is blocked, the thread that called the method can't do anything else. However, other threads in the same program can proceed. (This is why a server needs a separate thread just to wait for connection requests.) The ServerSocket will continue listening for connections until it is closed, using its close() method, or until some error occurs.
Suppose that you want a server to listen on port 1728. Each time the server receives a connection request, it should create a new thread to handle the connection with the client. Suppose that you've written a method createServiceThread(Socket) that creates such a thread. Then a simple version of the run() method for the server thread would be:
public run() { try { ServerSocket server = new ServerSocket(1728); while (true) { Socket connection = server.accept(); createServiceThread(connection); } } catch (IOException e) { System.out.println("Server shut down with error: " + e); } }On the client side, a client socket is created using a constructor in the Socket class. To connect to a server on a known computer and port, you would use the constructor
public Socket(String computer, int port) throws IOExceptionThis constructor will block until the connection is established or until an error occurs. (This means that even when you write a client program, you might want to use a separate thread to handle the connection, so that the program can continue to respond to user inputs while the connection is being established. Otherwise, the program will just freeze for some indefinite period of time.) Once the connection is established, you can use the methods getInputStream() and getOutputStream() to obtain streams that can be used for communication over the connection. Keeping all this in mind, here is the outline of a method for working with a client connection:
void doClientConnection(String computerName, int listeningPort) { // computerName should give the name of the computer // where the server is running, such as math.hws.edu; // listeningPort should be the port on which the server // listens for connections, such as 1728. Socket connection; InputStream in; OutputStream out; try { connection = new Socket(computerName,listeningPort); in = connection.getInputStream(); out = connection.getOutputStream(); } catch (IOException e) { System.out.println("Attempt to create connection failed with error: " + e); return; } . . // use the streams, in and out, to communicate with server . connection.close(); }All this makes network communication sound easier than it really is. (And if you think it sounded hard, then it's even harder.) If networks were completely reliable, things would be almost as easy as I've described. The problem, though, is to write robust programs that can deal with network and human error. I won't go into detail here -- partly because I don't really know enough about serious network programming in Java myself. However, what I've covered here should give you the basic ideas of network programming, and it is enough to write some simple network applications. (Just don't try to write a replacement for Netscape.)
End of Chapter 8
[ Next Chapter | Previous Section | Chapter Index | Main Index ]