Section 10.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. Nevertheless, 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 classes for this style of networking are java.net.URL and java.net.URLConnection. 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. A URLConnection represents a network connection to such a resource.
The second style of I/O views the network at a lower level. It 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. Communication over a network involves two sockets, one on each of the computers involved in the communication. Java uses a class called java.net.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 these basic networking classes, and shows how they relate to input and output streams and to exceptions.
URLs and URLConnections
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 6.2 for more information.
An object belonging to the URL class represents such an address. Once you have a URL object, you can use it to open a URLConnection to the resource at that address. 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 MalformedURLExceptionand
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. The MalformedURLException class is a subclass of IOException, and it requires mandatory exception handling. That is, you must call the constructor inside a try...catch statement that handles the exception or in a subroutine that is declared to throw the exception.
The second constructor is especially convenient when writing applets. In an applet, two methods are 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 url = 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 on which the applet is running. Another method, getCodeBase(), returns a URL that gives the location of the applet class file (which is not necessarily the same as the location of the document).
Once you have a valid URL object, you can call its openConnection() method to set up a connection. This method returns a URLConnection. The URLConnection object can, in turn, be used to create an InputStream for reading data from the resource represented by the URL. This is done by calling its getInputStream() method. For example:
URL url = new URL(urlAddressString); URLConnection connection = url.openConnection(); InputStream in = connection.getInputStream();The openConnection() and getInputStream() methods can both throw exceptions of type IOException. Once the InputStream has been created, you can read from it in the usual way, including wrapping it in another input steam type, such as TextReader. Reading from the stream can, of course, generate exceptions.
One of the other useful instance methods in the URLConnection class is getContentType(), which returns a String that describes the type of information available from the URL. The return value can be null if the type of information is not yet known or if it is not possible to determine the type. The type might not be available until after the input stream has been created, so you should generally call getContentType() after getInputStream(). The string returned by getContentType() is in a format called a mime type. Mime types include "text/plain", "text/html", "image/jpeg", "image/gif", and many others. All mime types contain two parts: a general type, such as "text" or "image", and a more specific type within that general category, such as "html" or "gif". If you are only interested in text data, for example, you can check whether the string returned by getContentType() starts with "text". (Mime types were first introduced to describe the content of email messages. The name stands for "Multipurpose Internet Mail Extensions." They are now used almost universally to specify the type of information in a file or other resource.)
Let's look at a short example that uses all this to read the data from a URL. This subroutine opens a connection to a specified URL, checks that the type of data at the URL is text, and the copies the text onto the screen. Many of the operations in this subroutine can throw exceptions. They are handled by declaring that the subroutine "throws Exception" and leaving it up to the main program to decide what to do when an error occurs.
static void readTextFromURL( String urlString ) throws Exception { // This subroutine attempts to copy text from the // specified URL onto the screen. All errors must // be handled by the caller of this subroutine. /* Open a connection to the URL, and get an input stream for reading data from the URL. */ URL url = new URL(urlString); URLConnection connection = url.openConnection(); InputStream urlData = connection.getInputStream(); /* Check that the content is some type of text. */ String contentType = connection.getContentType(); if (contentType == null || contentType.startsWith("text") == false) throw new Exception("URL does not refer to a text file."); /* Copy characters from the input stream to the screen, until end-of-file is encountered (or an error occurs). */ while (true) { int data = urlData.read(); if (data < 0) break; System.out.print((char)data); } } // end readTextFromURL()A complete program that uses this subroutine can be found in the file ReadURL.java. Here is an applet that does much the same thing (although it is more complex and uses some techniques that won't be covered until the next section). The applet lets you enter a URL. It can be a relative URL, which will be interpreted relative to the document base of the applet. Error messages or text loaded from the URL will be displayed in the text area of the applet. (The amount of text is limited to 10000 characters.) When the applet starts up, it is configured to load the file ReadURL.java. Just click the "Load" button:
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 other urls to see 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. To protect you from malicious applets, an applet is allowed to open network connections only back to the computer from which it came.) The source code for the applet is in the file ReadURLApplet.java
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 more basic communication protocol called UDP that can be used instead of TCP in certain applications. UDP is supported in Java, 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 uses 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. (To do this, it is necessary to use "threads". We'll look at how it works in the next section.)
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 socket is actually identified by a port number in combination with an IP 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 specification 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.) 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, and suppose that you've written a method provideService(Socket) to handle the communication with one client. Then the basic form of the server program would be:
try { ServerSocket server = new ServerSocket(1728); while (true) { Socket connection = server.accept(); provideService(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 IOExceptionThe first parameter can be either an IP number or a domain name. This constructor will block until the connection is established or until an error occurs. Once the connection is established, you can use the Socket 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 or ip number 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. . try { connection.close(); // (Alternatively, you might depend on the server // to close the connection.) } catch (IOException e) { } } // end doClientConnection()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.) Let's look at a few working examples of client/server programming.
Programming Examples
The first example consists of two programs. One is a simple client and the other is a matching server. The client makes a connection to the server, reads one line of text from the server, and displays that text on the screen. The text sent by the server consists of the current date and time on the computer where the server is running. In order to open a connection, the client must know the computer on which the server is running and the port on which it is listening. The server listens on port number 32007. The port number could be anything between 1025 and 65535, as long the server and the client use the same port. Port numbers between 1 and 1024 are reserved for standard services and should not be used for other servers. The name or IP number of the computer on which the server is running must be specified as a command-line parameter. For example, if the server is running on a computer named math.hws.edu, then you would typically run the client with the command "java DateClient math.hws.edu". Here is the complete client program:
import java.net.*; import java.io.*; public class DateClient { static final int LISTENING_PORT = 32007; public static void main(String[] args) { String computer; // Name of the computer to connect to. Socket connection; // A socket for communicating with // that computer. Reader incoming; // Stream for reading data from // the connection. /* Get computer name from command line. */ if (args.length > 0) computer = args[0]; else { // No computer name was given. Print a message and exit. System.out.println("Usage: java DateClient <server>"); return; } /* Make the connection, then read and display a line of text. */ try { connection = new Socket( computer, LISTENING_PORT ); incoming = new InputStreamReader( connection.getInputStream() ); while (true) { int ch = incoming.read(); if (ch == -1 || ch == '\n' || ch == '\r') break; System.out.print( (char)ch ); } System.out.println(); incoming.close(); } catch (IOException e) { TextIO.putln("Error: " + e); } } // end main() } // end class DateClientNote that all the communication with the server is done in a try...catch. This will catch the IOExceptions that can be generated when the connection is opened or closed and when characters are read from the stream. The stream that is used for input is a basic Reader, which includes the input operation incoming.read(). This function reads one character from the stream and returns its Unicode code number. If the end-of-stream has been reached, then the value -1 is returned instead. The while loop reads characters and copies them to standard output until an end-of-stream or end-of-line is seen. An end of line is marked by one of the characters '\n' or '\r', depending on the type of computer on which the server is running.
In order for this program to run without error, the server program must be running on the computer to which the client tries to connect. By the way, it's possible to run the client and the server program on the same computer. For example, you can open two command windows, start the server in one window and then run the client in the other window. To make things like this easier, most computers will recognize the IP number 127.0.0.1 as referring to "this computer". That is, the command "java DateClient 127.0.0.1" will tell the DateClient program to connect to a server running on the same computer. Most computers will also recognize the name "localhost" as a name for "this computer".
The server program that corresponds to the DateClient client program is called DateServe. The DateServe program creates a ServerSocket to listen for connection requests on port 32007. After the listening socket is created, the server will enter an infinite loop in which it accepts and processes connections. This will continue until the program is killed in some way -- for example by typing a CONTROL-C in the command window where the server is running. When a connection is received from a client, the server calls a subroutine to handle the connection. In the subroutine, any Exception that occurs is caught, so that it will not crash the server. The subroutine creates a PrintWriter stream for sending data over the connection. It writes the current date and time to this stream and then closes the connection. (The standard class java.util.Date is used to obtain the current time. An object of type Date represents a particular date and time. The default constructor, "new Date()", creates an object that represents the time when the object is created.) The complete server program is as follows:
import java.net.*; import java.io.*; import java.util.Date; public class DateServe { static final int LISTENING_PORT = 32007; public static void main(String[] args) { ServerSocket listener; // Listens for incoming connections. Socket connection; // For communication with the // connecting program. /* Accept and process connections forever, or until some error occurs. (Note that errors that occur while communicating with a connected program are caught and handled in the sendDate() routine, so they will not crash the server.) */ try { listener = new ServerSocket(LISTENING_PORT); TextIO.putln("Listening on port " + LISTENING_PORT); while (true) { connection = listener.accept(); sendDate(connection); } } catch (Exception e) { TextIO.putln("Sorry, the server has shut down."); TextIO.putln("Error: " + e); return; } } // end main() static void sendDate(Socket client) { // The parameter, client, is a socket that is // already connected to another program. Get // an output stream for the connection, send the // current date, and close the connection. try { System.out.println("Connection from " + client.getInetAddress().toString() ); Date now = new Date(); // The current date and time. PrintWriter outgoing; // Stream for sending data. outgoing = new PrintWriter( client.getOutputStream() ); outgoing.println( now.toString() ); outgoing.flush(); // Make sure the data is actually sent! client.close(); } catch (Exception e){ System.out.println("Error: " + e); } } // end sendDate() } //end class DateServeIf you run DateServe in a command-line interface, it will sit and wait for connection requests and report them as they are received. To make the DateServe service permanently available on a computer, the program really should be run as a daemon. A daeman is a program that runs continually on a computer, independently of any user. The computer can be configured to start the daemon automatically as soon as the computer boots up. It then runs in the background, even while the computer is being used for other purposes. For example, a computer that makes pages available on the World Wide Web runs a daemon that listens for requests for pages and responds by transmitting the pages. It's just a souped-up analog of the DateServe program! However, the question of how to set up a program as a daemon is not one I want to go into here. For testing purposes, it's easy enough to start the program by hand, and, in any case, my examples are not really robust enough or full-featured enough to be run as serious servers. (By the way, the word "daemon" is just an alternative spelling of "demon" and is usually pronounced the same way.)
Note that after calling out.println() to send a line of data to the client, the server program calls out.flush(). The flush() method is available in every output stream class. Calling it ensures that data that has been written to the stream is actually sent to its destination. You should call this function every time you use an output stream to send data over a network connection. If you don't do so, it's possible that the stream will collect data until it has a large batch of data to send. This is done for efficiency, but it can impose unacceptable delays when the client is waiting for the transmission. It is even possible that some of the data might remain untransmitted when the socket is closed, so it is especially important to call flush() before closing the connection. This is one of those unfortunate cases where different implementations of Java can behave differently. If you fail to flush your output streams, it is possible that your network application will work on some types of computers but not on others.
In the DateServe example, the server transmits information and the client reads it. It's also possible to have two-way communication between client and server. As a first example, we'll look at a client and server that allow a user on each end of the connection to send messages to the other user. The program works in a command-line interface where the users type in their messages. In this example, the server waits for a connection from a single client and then closes down its listener so that no other clients can connect. After the client and server are connected, both ends of the connection work in much the same way. The user on the client end types a message, and it is transmitted to the server, which displays it to the user on that end. Then the user of the server types a message that is transmitted to the client. Then the client user types another message, and so on. This continues until one user or the other enters "quit" when prompted for a message. When that happens, the connection is closed and both programs terminate. The client program and the server program are very similar. The techniques for opening the connections differ, and the client is programmed to send the first message while the server is programmed to receive the first message. Here is the server program. You can find the client program in the file CLChatClient.java. (The name "CLChat" stands for command-line chat.)
import java.net.*; import java.io.*; public class CLChatServer { static final int DEFAULT_PORT = 1728; // Port to listen on, // if none is specified // on the command line. static final String HANDSHAKE = "CLChat"; // Handshake string. // Each end of the connection sends this string // to the other just after the connection is // opened. This is done to confirm that the // program on the other side of the connection // is a CLChat program. static final char MESSAGE = '0'; // This character is added to // the beginning of each message // that is sent. static final char CLOSE = '1'; // This character is sent to // the connected program when // the user quits. public static void main(String[] args) { int port; // The port on which the server listens. ServerSocket listener; // Listens for a connection request. Socket connection; // For communication with the client. TextReader incoming; // Stream for receiving data from client. PrintWriter outgoing; // Stream for sending data to client. String messageOut; // A message to be sent to the client. String messageIn; // A message received from the client. /* First, get the port number from the command line, or use the default port if none is specified. */ if (args.length == 0) port = DEFAULT_PORT; else { try { port = Integer.parseInt(args[0]); if (port < 0 || port > 65535) throw new NumberFormatException(); } catch (NumberFormatException e) { TextIO.putln("Illegal port number, " + args[0]); return; } } /* Wait for a connection request. When it arrives, close down the listener. Create streams for communication and exchange the handshake. */ try { listener = new ServerSocket(port); TextIO.putln("Listening on port " + listener.getLocalPort()); connection = listener.accept(); listener.close(); incoming = new TextReader(connection.getInputStream()); outgoing = new PrintWriter(connection.getOutputStream()); outgoing.println(HANDSHAKE); outgoing.flush(); messageIn = incoming.getln(); if (! messageIn.equals(HANDSHAKE) ) { throw new IOException("Connected program is not CLChat!"); } TextIO.putln("Connected. Waiting for the first message.\n"); } catch (Exception e) { TextIO.putln("An error occurred while opening connection."); TextIO.putln(e.toString()); return; } /* Exchange messages with the other end of the connection until one side or the other closes the connection. This server program waits for the first message from the client. After that, messages alternate strictly back and forth. */ try { while (true) { TextIO.putln("WAITING..."); messageIn = incoming.getln(); if (messageIn.length() > 0) { // The first character of the message is a command. // If the command is CLOSE, then the connection // is closed. Otherwise, remove the command // character from the message and proceed. if (messageIn.charAt(0) == CLOSE) { TextIO.putln("Connection closed at other end."); connection.close(); break; } messageIn = messageIn.substring(1); } TextIO.putln("RECEIVED: " + messageIn); TextIO.put("SEND: "); messageOut = TextIO.getln(); if (messageOut.equalsIgnoreCase("quit")) { // User wants to quit. Inform the other side // of the connection, then close the connection. outgoing.println(CLOSE); outgoing.flush(); // Make sure the data is sent! connection.close(); TextIO.putln("Connection closed."); break; } outgoing.println(MESSAGE + messageOut); outgoing.flush(); // Make sure the data is sent! if (outgoing.checkError()) { throw new IOException( "Error occurred while transmitting message."); } } } catch (Exception e) { TextIO.putln("Sorry, an error has occurred. Connection lost."); TextIO.putln(e.toString()); System.exit(1); } } // end main() } //end class CLChatServerThis program is a little more robust than DateServe. For one thing, it uses a handshake to make sure that a client who is trying to connect is really a CLChatClient program. A handshake is simply information sent between client and server as part of setting up the connection. In this case, each side of the connection sends a string to the other side to identify itself. The handshake is part of the protocol that I made up for communication between CLChatClient and CLChatServer. When you design a client/server application, the design of the protocol is an important consideration. Another aspect of the CLChat protocol is that every line of text that is sent over the connection after the handshake begins with a character that acts as a command. If the character is 0, the rest of the line is a message from one user to the other. If the character is 1, the line indicates that a user has entered the "quit" command, and the connection is to be shut down.
Remember that if you want to try out this program on a single computer, you can use two command-line windows. In one, give the command "java CLChatServer" to start the server. Then, in the other, use the command "java CLChatClient 127.0.0.1" to connect to the server that is running on the same machine.
[ Next Section | Previous Section | Chapter Index | Main Index ]