by Mark Wutka
The java.net package provides low-level and high-level network functionality. The high-level networking classes allow you to access information by specifying the type and location of the information. You can access information from a Web server, for instance. The high-level classes take care of the drudgery of networking protocols and allow you to concentrate on the actual information. If you need finer control than this, you can use the low-level classes. These classes let you send raw data over the network. You can use them to implement your own networking protocols.
The URL class represents a Uniform Resource Locator, which is the standard address format for resources on the World Wide Web as defined in the Internet standard RFC 1630. An URL is similar to a file name in that it tells where to go to get some information, but you still have to open and read it to get the information. Once you create an URL, you can retrieve the information stored at that URL in one of three ways:
You have a number of options when it comes to creating an URL object. You can call the constructor with a string representing the full URL:
public URL(String fullURL) throws MalformedURLException
The full URL string is the form you are probably most familiar with. Here is an example:
URL queHomePage = new URL("http://www.quecorp.com");
You can also create an URL by giving the protocol, host name, file name, and an optional port number:
public URL(String protocol, String hostName, String fileName) throws MalformedURLException public URL(String protocol, String hostName, int portNumber, String fileName) throws MalformedURLException
The equivalent of the Que home page URL using this notation would be
URL queHomePage = new URL("http", "www.quecorp.com", "que");
or
URL queHomePage = new URL("http", "www.quecorp.com", 80, "que"); // 80 is default http port
If you have already created an URL and would like to open a new URL based on some information from the old one, you can pass the old URL and a string to the URL constructor:
public URL(URL contextURL, String spec)
This is most often used in applets because the Applet class returns an URL for the directory where the applet's .class file resides. You can also get an URL for the directory where the applet's document is stored. For example, suppose you stored a file called myfile.txt in the same directory as your applet's .html file. Your applet could create the URL for myfile.txt with
URL myfileURL = new URL(getDocumentBase(), "myfile.txt");
If you had stored myfile.txt in the same directory as the applet's .class file (it may or may not be the same directory as the .html file), the applet could create an URL for myfile.txt with
URL myfileURL = new URL(getCodeBase(), "myfile.txt");
Once you create an URL, you will probably want to fetch the contents. The easiest way to do this is by calling the getContent method:
public final Object getContent()
This first method requires that you define a content handler for the content returned by the URL. The HotJava browser comes with some built-in content handlers, but Netscape does not use this method for interpreting content. You will likely get an UnknownServiceException if you use this method from Netscape.
If you would rather interpret the data yourself, you can get an URLConnection for an URL with the openConnection method:
public URLConnection openConnection() throws IOException
Your third option for getting the contents of an URL should work almost everywhere. You can get an input stream to the URL and read it in yourself by using the openStream method:
public final InputStream openStream() throws IOException
The following code fragment dumps the contents of an URL to the System.out stream by opening an input stream to the URL and reading one byte at a time:
try {
URL myURL = new URL(getDocumentBase(), "foo.html"); InputStream in = myURL.openStream(); // get input stream for URL int b; while ((b = in.read()) != -1) { // read the next byte System.out.print((char)b); // print it } } catch (Exception e) { e.printStackTrace(); // something went wrong }
You can retrieve the specific pieces of an URL using the following methods:
public String getProtocol()
returns the name of the URL's protocol.
public String getHost()
returns the name of the URL's host.
public int getPort()
returns the URL's port number.
public String getFile()
returns the URL's file name.
public String getRef()
returns the URL's reference tag. This is an optional index into an HTML page that follows the file name and begins with a #.
The URLConnection class provides a more granular interface to an URL than the getContent method in the URL class. This class provides methods for examining HTTP headers, getting information about the URL's content, and getting input and output streams to the URL. There will be a different URLConnection class for each type of protocol that you can use. For instance, there will be an URLConnection that handles the HTTP protocol, as well as another that handles the FTP protocol. Your browser may not support any of them. You can feel fairly certain that they are implemented in HotJava. HotJava is written totally in Java, which uses these classes to do all of its browsing. Netscape, on the other hand, has its own native code for handling these protocols and does not use Sun's URLConnection classes.
This class is geared toward interpreting text that will be displayed in a browser. Consequently, it has many methods for dealing with header fields and content types.
You do not create an URLConnection object yourself; it is created and returned by an URL object. Once you have an instance of an URLConnection, you can examine the various header fields with the getHeaderField methods:
public String getHeaderField(String fieldName)
returns the value of the header field named by fieldName. If this field is not present in the resource, this method returns null.
public String getHeaderField(int n)
returns the value of the nth field in the resource. If there are not that many header fields, this method returns null. You can get the corresponding field name with the getHeaderFieldKey method.
public int getHeaderFieldKey(int n)
returns the field name of the nth field in the resource. If there are not that many header fields, this method returns null.
You can also get a header field value as an integer or a date using the following methods:
public int getHeaderFieldInt(String fieldName, int defaultValue)
converts the header field named by fieldName to an integer. If the field does not exist or is not a valid integer, it returns defaultValue.
public int getHeaderFieldDate(String fieldName, long defaultValue)
interprets the header field value as a date and returns the number of milliseconds since the epoch for that date. If the field does not exist or is not a valid date, it returns defaultValue.
In addition to interpreting the header fields, the URLConnection class also returns information about the content:
public String getContentEncoding() public int getContentLength() public String getContentType()
As with the URL class, you can get the entire content of the URL as an object using the getContent method:
public Object getContent() throws IOException, UnknownServiceException
This method probably won't work under Netscape but should work under HotJava.
Sometimes a program tries to access an URL that requires user authentication in the form of a dialog box, which automatically pops up when you open the URL. Because you do not always want your Java program to require that a user be present, you can tell the URLConnection class whether it should allow user interaction. If a situation occurs that requires user interaction and you have turned it off, the URLConnection class will throw an exception.
The setAllowUserInteraction method, when passed a value of true, will permit interaction with a user when needed:
public void setAllowUserInteraction(boolean allowInteraction) public boolean getAllowUserInteraction()
returns true if this class will interact with a user when needed.
public static void setDefaultAllowUserInteraction(boolean default)
changes the default setting for allowing user interaction on all new instances of URLConnection. Changing the default setting does not affect instances that have already been created.
public static boolean getDefaultAllowUserInteraction()
returns the default setting for allowing user interaction.
Some URLs allow two-way communication. You can tell an URLConnection whether it should allow input or output by using the doInput and doOutput methods:
public void setDoInput(boolean doInput) public void setDoOutput(boolean doOutput)
You can set either or both of these values to true. The doInput flag is true by default, while the doOutput flag is false by default.
You can query the doInput and doOutput flags with getDoInput and getDoOutput:
public boolean getDoInput() public boolean getDoOutput()
The getInputStream and getOutputStream methods return input and output streams for the resource:
public InputStream getInputStream() throws IOException, UnknownServiceException public OutputStream getOutputStream() throws IOException, UnknownServiceException
The HTTP protocol has some extra features that the URLConnection class does not address. For instance, when you send an HTTP request, there are several different requests you can make (GET, POST, PUT, and so on). The HTTPURLConnection class provides better access to HTTP-specific options.
One of the most important fields in the HTTPURLConnection is the request method. You can set the request method by calling setRequestMethod with the name of the method you want:
public void setRequestMethod(String method) throws ProtocolException
The valid methods are: GET, POST, HEAD, PUT, DELETE, OPTIONS, and TRACE. If you don't set a request method, the default method is GET. Calling getRequestMethod will return the current method:
public String getRequestMethod()
When you send an HTTP request, the HTTP server responds with a response code and message. For example, if you try to access a Web page that no longer exists, you get a "404 Not Found" message. The getResponseMessage method returns the message part of a response while the getResponseCode returns the numeric portion:
public String getResponseMessage() throws IOException public int getResponseCode() throws IOException
In the case of "404 Not Found", getResponseCode would return 404, while getResponseMessage would return "Not Found".
Since Web sites move around frequently, Web servers support the notion of redirection, where you are automatically sent to a page's new location. The HTTPURLConnection class allows you to choose whether it should automatically follow a redirection or not. Passing a flag value of true to setFollowRedirects method instructs the HTTPURLConnection class to follow a redirection:
public static void setFollowRedirects(boolean flag)
The getFollowRedirects method returns true if redirection is turned on:
public static boolean getFollowRedirects()
The getProxy method returns true if all HTTP requests are going through a proxy:
public abstract boolean usingProxy()
This class contains only one static method that converts a string into URL-encoded form. The URL encoding reduces a string to a limited set of characters. Only letters, digits, and the underscore character are left untouched. Spaces are converted to a +, and all other characters are converted to hexadecimal and written as %xx, where xx is the hex representation of the character. The format for the encode method is
public static String encode(String s)
The URLStreamHandler class is responsible for parsing an URL and creating an URLConnection object to access that URL. When you open a connection for an URL, it scans a set of packages for a handler for that URL's protocol. The handler should be named <protocol>.Handler. For instance, if you open an HTTP URL, the URL class searches for a class named <some package name>.http.Handler. By default, the class only searches the package sun.net.www.protocol, but you may specify an alternate search path by setting the system property to java.protocol.handler.pkgs. This property should contain a list of alternate packages to search that are separated by vertical bars--for example,
mypackages.urls|thirdparty.lib|funstuff".
At the minimum, any subclass of the URLStreamHandler must implement an openConnection method:
protected abstract URLConnection openConnection(URL u) throws IOException
This method returns an instance of URLConnection that knows how to speak the correct protocol. For instance, if you create your own URLStreamHandler for the FTP protocol, this method should return an URLConnection that speaks the FTP protocol.
You can also change the way an URL string is parsed by creating your own parseURL and setURL methods:
protected void parseURL(URL u, String spec, int start, int limit)
This method parses an URL string, starting at position start in the string and going up to position limit. It modifies the URL directly, once it has parsed the string, using the protected set method in the URL.
You can set the different parts of an URL's information using the setURL method:
protected void setURL(URL u, String protocol, String host, int port, String file, String ref)
The call to set looks like the following:
u.set(protocol, host, port, file, ref);
NOTE: Most of the popular network protocols are already implemented in the HotJava browser. If you want to use the URLStreamHandler facility in Netscape and other browsers, you need to write many of these yourself.
When you fetch a document using the HTTP protocol, the Web server sends you a series of headers before sending the actual data. One of the items in this header indicates what kind of data is being sent. This data is referred to as content, and the type of the data (referred to as the MIME content-type) is specified by the Content-type header. Web browsers use the content type to determine what to do with the incoming data.
If you want to provide your own handler for a particular MIME content-type, you can create a ContentHandler class to parse it and return an object representing the contents. The mechanism for setting up your own content handler is almost identical to that of setting up your own URLStreamHandler. You must give it a name of the form <some package name>.major.minor. The major and minor names come from the MIME Content-type header, which is in the following form:
Content-type: major/minor
One of the most common major/minor combinations is text/plain. If you define your own text/plain handler, it can be named MyPackage.text.plain. By default, the URLConnection class searches for content handlers only in a package named sun.net.www.content. You can give additional package names by specifying a list of packages separated by vertical bars in the java.content.handler.pkgs system property.
The only method you must implement in your ContentHandler is the getContent method:
public abstract Object getContent(URLConnection urlConn) throws IOException
It is completely up to you how you actually parse the content and select the kind of object you return.
The Socket class is one of the fundamental building blocks for network-based Java applications. It implements a two-way connection-oriented communications channel between programs. Once a socket connection is established, you can get input and output streams from the Socket object. In order to establish a socket connection, a program must be listening for connections on a specific port number. Although socket communications are peer-to-peer--that is, neither end of the socket connection is considered the master, and data can be sent either way at any time--the connection establishment phase has a notion of a server and a client.
Think of a socket connection as a phone call. Once the call is made, either party can talk at any time, but when the call is first made, someone must make the call and someone else must listen for the phone to ring. The person making the call is the client, and the person listening for the call is the server.
The ServerSocket class, discussed later in this chapter, listens for incoming calls. The Socket class initiates a call. The network equivalent of a telephone number is a host address and port. The host address can either be a host name, like netcom.com, or a numeric address, like 192.100.81.100. The port number is a 16-bit number that is usually determined by the server. When you create a Socket object, you pass the constructor the destination host name and port number for the server you are connecting to. For example,
public Socket(String host, int port) throws UnknownHostException, IOException
creates a socket connection to port number port at the host named by host. If the Socket class cannot determine the numeric address for the host name, it throws an UnknownHostException. If there is a problem creating the connection--for instance, if there is no server listening at that port number--you get an IOException.
NOTE: If you want to create a connection using a numeric host address, you can pass the numeric address as a host name string. For instance, the host address 192.100.81.100 can be passed as the host name "192.100.81.100".
public Socket(String host, int port, boolean stream) throws UnknownHostException, IOException
creates a socket connection to port number port at the host named by host. You can optionally request this connection be made by using datagram-based communication instead of stream-based. With a stream, you are assured that all the data sent over the connection will arrive correctly. Datagrams are not guaranteed, however, so it is possible that messages can be lost. The tradeoff here is that the datagrams are much faster than the streams, so, if you have a reliable network, you may be better off with a datagram connection. The default mode for Socket objects is stream mode. If you pass false for the stream parameter, the connection will be made in datagram mode. You cannot change modes once the Socket object has been created.
public Socket(InetAddress address, int port) throws IOException
creates a socket connection to port number port at the host whose address is stored in address.
public Socket(InetAddress address, int port, boolean stream) throws IOException
creates a socket connection to port number port at the host whose address is stored in address. If the stream parameter is false, the connection is made in datagram mode.
Because of security restrictions in Netscape and other browsers, you may be restricted to making socket connections back to the host address from where the applet was loaded.
The Socket class does not contain explicit methods for sending and receiving data. Instead, it provides methods that return input and output streams, allowing you to take full advantage of the existing classes in java.io.
The getInputStream method returns an InputStream for the socket, while the getOutputStream method returns an OutputStream:
public InputStream getInputStream() throws IOException public OutputStream getOutputStream() throws IOException
You can get information about the socket connection such as the address, the port it is connected to, and its local port number.
Just as each telephone in a telephone connection has its own phone number, each end of a socket connection has a host address and port number. The port number on the client side, however, does not enter into the connection establishment. One difference between socket communications and the telephone is that a client usually has a different port number every time it creates a new connection, but you always have the same phone number when you pick up the phone to make a call.
The getInetAddress and getPort methods return the host address and port number for the other end of the connection:
public InetAddress getInetAddress() public int getPort()
You can get the local port number of your socket connection from the getLocalPort method:
public int getLocalPort()
There are certain socket options that modify the behavior of sockets. They are not often used, but it is nice to have them available. The setSoLinger method sets the amount of time that a socket will spend trying to send data after it has been closed:
public void setSoLinger(boolean on, int maxTime) throws SocketException
Normally, when you are sending data over a socket and you close the socket, any untran- smitted data is flushed. By turning on the linger option you can make sure that all data has been sent before the socket connection is taken down. You can query the linger time with getSoLinger:
public int getSoLinger() throws SocketException
If the linger option is off, getSoLinger returns -1.
If you try to read data from a socket and there is no data available, the read method normally blocks (it waits until there is data). You can use the setSoTimeout method to set the maximum amount of time that the read method will wait before giving up:
public synchronized void setSoTimeout(int timeout) throws SocketException
A timeout of 0 indicated that the read method should wait forever (the default behavior). If the read times out, instead of just returning, it will throw java.io.InterruptedIOException, but the socket will remain open. You can query the current timeout with getSoTimeout:
public synchronized int getSoTimeout() throws SocketException
The TCP protocol used by socket connections is reasonably efficient in network utilization. If it is sending large amounts of data, it usually packages the data into larger packets. The reason this is more efficient is that there is a certain fixed amount of overhead per network packet. If the packets are larger, the percentage of network bandwidth consumed by the overhead is much smaller. Unfortunately, TCP can also cause delays when you are sending many small packets in a short amount of time. For instance, if you are sending mouse coordinates over the network, the TCP driver will frequently group the coordinates into larger packets while it is waiting for acknowledgment that the previous packets were received. This makes the mouse movement look pretty choppy. You can ask the socket to send information as soon as possible by passing true to setTcpNoDelay:
public void setTcpNoDelay(boolean on)
The getTcpNoDelay method returns true if the socket is operating under the "no delay" option (if the socket sends things immediately):
public boolean getTcpNoDelay()
CAUTION:
You should be very careful when using the no delay option. If you send a flurry of small packets, you can waste large amounts of network bandwidth. If you send a 1-byte message, given about 64 bytes of fixed overhead, 98 percent of the bandwidth you use is for overhead. Even for a 64-byte message, 50 percent of the bandwidth is overhead.
The socket equivalent of "hanging up the phone" is closing down the connection, which is performed by the close method:
public synchronized void close() throws IOException
Reading data from a socket is not quite like reading data from a file, even though both are input streams. When you read a file, all of the data is already in the file. But with a socket connection, you may try to read before the program on the other end of the connection has sent something. Because the read methods in the different input streams all block--that is, they wait for data if none is present--you must be careful that your program does not completely halt while waiting. The typical solution for this situation is to spawn a thread to read data from the socket. Listing 34.1 shows a thread that is dedicated to reading data from an input stream. It notifies your program of new data by calling a dataReady method with the incoming data.
import java.net.*; import java.lang.*; import java.io.*; /** * A thread dedicated to reading data from a socket connection. */
public class ReadThread extends Thread { protected Socket connectionSocket; // the socket you are reading from protected DataInputStream inStream; // the input stream from the socket protected ReadCallback readCallback; /** * Creates an instance of a ReadThread on a Socket and identifies the callback * that will receive all data from the socket. * * @param callback the object to be notified when data is ready * @param connSock the socket this ReadThread will read data from * @exception IOException if there is an error getting an input stream * for the socket */ public ReadThread(ReadCallback callback, Socket connSock) throws IOException { connectionSocket = connSock; readCallback = callback; inStream = new DataInputStream(connSock.getInputStream()); } /** * Closes down the socket connection using the socket's close method */ protected void closeConnection() { try { connectionSocket.close(); } catch (Exception oops) { } stop(); } /** * Continuously reads a string from the socket and then calls dataReady in the * read callback. If you want to read something other than a string, change * this method and the dataReady callback to handle the appropriate data. */ public void run() { while (true) { try { // readUTF reads in a string String str = inStream.readUTF(); // Notify the callback that you have a string readCallback.dataReady(str); } catch (Exception oops) { // Tell the callback there was an error readCallback.dataReady(null); } } } }
Listing 34.2 shows the ReadCallback interface, which must be implemented by a class to receive data from a ReadThread object.
/** * Implements a callback interface for the ReadConn class */ public interface ReadCallback { /** * Called when there is data ready on a ReadConn connection. * @param str the string read by the read thread, If null, the * connection closed or there was an error reading data */ public void dataReady(String str); }
Using these two classes, you can implement a simple client that connects to a server and uses a read thread to read the data returned by the server. The corresponding server for this client is presented in the next section, "The ServerSocket Class." Listing 34.3 shows the SimpleClient class.
import java.io.*; import java.net.*; /** * This class sets up a Socket connection to a server, spawns * a ReadThread object to read data coming back from the server, * and starts a thread that sends a string to the server every * 2 seconds. */ public class SimpleClient extends Object implements Runnable, ReadCallback { protected Socket serverSock; protected DataOutputStream outStream; protected Thread clientThread; protected ReadThread reader; public SimpleClient(String hostName, int portNumber) throws IOException { Socket serverSock = new Socket(hostName, portNumber); // The DataOutputStream has methods for sending different data types // in a machine-independent format. It is very useful for sending data // over a socket connection. outStream = new DataOutputStream(serverSock.getOutputStream()); // Create a reader thread reader = new ReadThread(this, serverSock); // Start the reader thread reader.start(); } // These are generic start and stop methods for a Runnable public void start() { clientThread = new Thread(this); clientThread.start(); } public void stop() { clientThread.stop(); clientThread = null; } // sendString sends a string to the server using writeUTF public synchronized void sendString(String str) throws IOException { System.out.println("Sending string: "+str); outStream.writeUTF(str); } // The run method for this object just sends a string to the server // and sleeps for 2 seconds before sending another string public void run() { while (true) { try { sendString("Hello There!"); Thread.sleep(2000); } catch (Exception oops) { // If there was an error, print info and disconnect oops.printStackTrace(); disconnect(); stop(); } } } // The disconnect method closes down the connection to the server public void disconnect() { try { reader.closeConnection(); } catch (Exception badClose) { // should be able to ignore } } // dataReady is the callback from the read thread. It is called // whenever a string is received from the server. public synchronized void dataReady(String str) { System.out.println("Got incoming string: "+str); } public static void main(String[] args) { try { /* Change localhost to the host you are running the server on. If it is on the same machine, you can leave it as localhost. */ SimpleClient client = new SimpleClient("localhost", 4331); client.start(); } catch (Exception cantStart) { System.out.println("Got error"); cantStart.printStackTrace(); } } }
The ServerSocket class listens for incoming connections and creates a Socket object for each new connection. You create a server socket by giving it a port number to listen on:
public ServerSocket(int portNumber) throws IOException
If you do not care what port number you are using, you can have the system assign the port number for you by passing in a port number of 0.
Many socket implementations have a notion of connection backlog. That is, if many clients connect to a server at once, the number of connections that have yet to be accepted are the backlog. Once a server hits the limit of backlogged connections, the server refuses any new clients. To create a ServerSocket with a specific limit of backlogged connections, pass the port number and backlog limit to the constructor:
public ServerSocket(int portNumber, int backlogLimit) throws IOException
NOTE: Because of current security restrictions in Netscape and other browsers, you may not be able to accept socket connections with an applet.
Once the server socket is created, the accept method will return a Socket object for each new connection:
public Socket accept() throws IOException
If no connections are pending, the accept method will block until there is a connection. If you do not want your program to block completely while you are waiting for connections, you should perform the accept in a separate thread.
When you no longer want to accept connections, close down the ServerSocket object with the close method:
public void close() throws IOException
The close method does not affect the existing socket connections that were made through this ServerSocket. If you want the existing connections to close, you must close each one explicitly.
If you need to find the address and port number for your server socket, you can use the getInetAddress and getLocalPort methods:
public InetAddress getInetAddress() public int getLocalPort()
The getLocalPort method is especially useful if you had the system assign the port number. You may wonder what use it is for the system to assign the port number because you somehow must tell the clients what port number to use. There are some practical uses for this method, however. One use is implementing the FTP protocol. If you have ever watched an FTP session in action, you will notice that when you get or put a file, a message such as PORT command accepted appears. What has happened is that your local FTP program created the equivalent of a server socket and sent the port number to the FTP server. The FTP server then creates a connection back to your FTP program using this port number.
There are many models you can use when writing a server program. For instance, you can make one big server object that accepts new clients and contains all the necessary methods for communicating with them. You can make your server more modular by creating special objects that communicate with clients but invoke methods on the main server object. Using this model, you can have clients who all share the server's information but can communicate using different protocols.
Listing 34.4 shows an example client handler object that talks to an individual client and passes the client's request up to the main server.
import java.io.*; import java.net.*; /** * This class represents a server's client. It handles all the * communications with the client. When the server gets a new * connection, it creates one of these objects, passing it the * Socket object of the new client. When the client's connection * closes, this object goes away quietly. The server doesn't actually * have a reference to this object. * * Just for example's sake, when you write a server using a setup * like this, you will probably have methods in the server that * this object will call. This object keeps a reference to the server * and calls a method in the server to process the strings read from * the client and returns a string to send back. */ public class ServerConn extends Object implements ReadCallback { protected SimpleServer server; protected Socket clientSock; protected ReadThread reader; protected DataOutputStream outStream; public ServerConn(SimpleServer server, Socket clientSock) throws IOException { this.server = server; this.clientSock = clientSock; outStream = new DataOutputStream(clientSock.getOutputStream()); reader = new ReadThread(this, clientSock); reader.start(); } /** * This method received the string read from the client, calls * a method in the server to process the string, and sends back * the string returned by the server. */ public synchronized void dataReady(String str) { if (str == null) { disconnect(); return; } try { outStream.writeUTF(server.processString(str)); } catch (Exception writeError) { writeError.printStackTrace(); disconnect(); return; } } /** * This method closes the connection to the client. If there is an error * closing the socket, it stops the read thread, which should eventually * cause the socket to get cleaned up. **/ public synchronized void disconnect() { try { reader.closeConnection(); } catch (Exception cantclose) { reader.stop(); } } }
With the ServerConn object handling the burden of communicating with the clients, your server object can concentrate on implementing whatever services it should provide. Listing 34.5 shows a simple server that takes a string and sends back the reverse of the string.
import java.io.*; import java.net.*; /** * This class implements a simple server that accepts incoming * socket connections and creates a ServerConn instance to handle * each connection. It also provides a processString method that * takes a string and returns the reverse of it. This method is * invoked by the ServerConn instances when they receive a string * from a client. */ public class SimpleServer extends Object { protected ServerSocket listenSock; public SimpleServer(int listenPort) throws IOException { // Listen for connections on port listenPort listenSock = new ServerSocket(listenPort); } public void waitForClients() { while (true) { try { // Wait for the next incoming socket connection Socket newClient = listenSock.accept(); // Create a ServerConn to handle this new connection ServerConn newConn = new ServerConn( this, newClient); } catch (Exception badAccept) { badAccept.printStackTrace(); // print an error, but keep going } } } // This method takes a string and returns the reverse of it public synchronized String processString(String inStr) { StringBuffer newBuffer = new StringBuffer(); int len = inStr.length(); // Start at the end of the string and move down towards the beginning for (int i=len-1; i >= 0; i--) { // Add the next character to the end of the string buffer // Since you started at the end of the string, the first character // in the buffer will be the last character in the string newBuffer.append(inStr.charAt(i)); } return newBuffer.toString(); } public static void main(String[] args) { try { // Crank up the server and wait for connection SimpleServer server = new SimpleServer(4321); server.waitForClients(); } catch (Exception oops) { // If there was an error starting the server, say so! System.out.println("Got error:"); oops.printStackTrace(); } } }
The InetAddress class contains an Internet host address. Internet hosts are identified one of two ways:
The address is a four-byte number that is usually written in the form a.b.c.d, like 192.100.81.100. When data is sent between computers, the network protocols use this numeric address for determining where to send the data. Host names are created for convenience. They keep you from having to memorize a lot of 12-digit network addresses. For example, it is far easier to remember netcom.com than it is to remember 192.100.81.100.
As it turns out, relating a name to an address is a science in itself. When you make a connection to netcom.com, your system needs to find out the numeric address for netcom. It will usually use a service called Domain Name Service, or DNS. DNS is the telephone book service for Internet addresses. Host names and addresses on the Internet are grouped into domains and subdomains, and each subdomain may have its own DNS--that is, its own local phone book.
You may have noticed that Internet host names are usually a number of names that are separated by periods. These separate names represent the domain a host belongs to. For example, netcom5.netcom.com is the host name for a machine named netcom5 in the netcom.com domain. The netcom.com domain is a subdomain of the .com domain. A netcom.edu domain could be completely separate from the netcom.com domain, and netcom5.netcom.edu would be a totally different host. Again, this is not too different from phone numbers. For example, the phone number 404-555-1017 has an area code of 404, which could be considered the Atlanta domain. The exchange 555 is a subdomain of the Atlanta domain, while 1017 is a specific number in the 555 domain, which is part of the Atlanta domain. Just as you can have a netcom5.netcom.edu that is different from netcom5.netcom.com, you can have an identical phone number in a different area code, such as 212-555-1017.
The important point to remember here is host names are only unique within a particular domain. Don't think that your organization is the only one in the world to have named its machines after The Three Stooges, Star Trek characters, or characters from various comic strips.
The InetAddress class handles all the intricacies of name lookup for you. The getByName method takes a host name and returns an instance of InetAddress that contains the network address of the host:
public static synchronized InetAddress getByName(String host) throws UnknownHostException
A host can have multiple network addresses. For example, suppose you have your own LAN at home as well as a PPP connection to the Internet. The machine with the PPP connection has two network addresses: the PPP address and the local LAN address. You can find out all of the available network addresses for a particular host by calling getAllByName:
public static synchronized InetAddress[] getAllByName(String host) throws UnknownHostException
The getLocalHost method returns the address of the local host:
public static InetAddress getLocalHost() throws UnknownHostException
The InetAddress class has two methods for retrieving the address that it stores. The getHostName method returns the name of the host, while getAddress returns the numeric address of the host:
public String getHostName() public byte[] getAddress()
The getAddress method returns the address as an array of bytes. Under the current Internet addressing scheme, an array of four bytes would be returned. However, if and when the Internet goes to a larger address size, this method simply returns a larger array. The following code fragment prints out a numeric address using the dot notation:
byte[] addr = someInetAddress.getAddress(); System.out.println((addr[0]&0xff)+"."+(addr[1]&0xff)+"."+ (addr[2]&0xff)+"."+(addr[3]&0xff));
You may be wondering why the address values are ANDed with the hex value ff (255 in decimal). The reason is that byte values in Java are signed 8-bit numbers. That means that when the leftmost bit is 1, the number is negative. Internet addresses are not usually written with negative numbers. By ANDing the values with 255, you do not change the value, but you suddenly treat the value as a 32-bit integer value whose leftmost bit is 0, and whose rightmost 8 bits represent the address.
Under many Java-aware browsers, socket connections are restricted to the server where the applet originated. In other words, the only host your applet can connect to is the one it was loaded from. You can create an instance of an InetAddress corresponding to the applet's originating host by getting the applet's document base or code base URL and then getting the URL's host name. The following code fragment illustrates this method:
URL appletSource = getDocumentBase(); // must be called from applet InetAddress appletAddress = InetAddress.getByName( appletSource.getHost());
The DatagramSocket class implements a special kind of socket that is made specifically for sending datagrams. A datagram is somewhat like a letter in that it is sent from one point to another and can occasionally get lost. Of course, Internet datagrams are several orders of magnitude faster than the postal system. A datagram socket is like a mailbox. You receive all your datagrams from your datagram socket. Unlike the stream-based sockets you read about earlier, you do not need a new datagram socket for every program you must communicate with.
If the datagram socket is the network equivalent of a mailbox, then the datagram packet is the equivalent of a letter. When you want to send a datagram to another program, you create a DatagramPacket object that contains the host address and port number of the receiving DatagramSocket, just like you must put an address on a letter when you mail it. You then call the send method in your DatagramSocket, and it sends your datagram packet off through the ethernet network to the recipient.
Not surprisingly, working with datagrams involves some of the same problems as mailing letters. Datagrams can get lost and delivered out of sequence. If you write two letters to someone, you have no guarantee which letter the person will receive first. If one letter refers to the other, it could cause confusion. There is no easy solution for this situation except to plan for the possibility.
Another situation occurs when a datagram gets lost. Imagine that you have mailed off your house payment, and a week later the bank tells you it hasn't received it. You don't know what happened to the payment--maybe the mail is very slow, or maybe the payment was lost. If you mail off another payment, maybe the bank will end up with two checks from you, but if you don't mail it off and the payment really is lost, the bank will be very angry. This, too, can happen with datagrams. You may send a datagram, not hear any reply, and assume it was lost. If you send another one, the server on the other end may get two requests and become confused. A good way to minimize the impact of this kind of situation is to design your applications so that multiple datagrams of the same information do not cause confusion. The specifics of this design are beyond the scope of this book. You should consult a good book on network programming.
You can create a datagram socket with or without a specific port number:
public DatagramSocket() throws SocketException public DatagramSocket(int portNumber) throws SocketException
As with the Socket class, if you do not give a port number, one will be assigned automatically. You only need to use a specific port number when other programs need to send unsolicited datagrams to you. Whenever you send a datagram, it has a return address on it, just like a letter. If you send a datagram to another program, it can always generate a reply to you without you explicitly telling it what port you are on. In general, only your server program needs to have a specific port number. The clients who send datagrams to the server and receive replies from it can have system-assigned port numbers, since the server can see the return address on their datagrams.
The mechanism for sending and receiving datagrams is about as easy as mailing a letter and checking your mailbox--most of the work is in writing and reading the letter. The send method sends a datagram to its destination (the destination is stored in the DatagramPacket object):
public void send(DatagramPacket packet) throws IOException
The receive method reads in a datagram and stores it in a DatagramPacket object:
public synchronized void receive(DatagramPacket packet) throws IOException
When you no longer need the datagram socket, you can close it down with the close method:
public synchronized void close()
Finally, if you need to know the port number of your datagram socket, the getLocalPort method gives it to you:
public int getLocalPort()
The DatagramPacket class is the network equivalent of a letter. It contains an address and other information. When you create a datagram, you must give it an array to contain the data as well as the length of the data. The DatagramPacket class is used in two ways:
To create a datagram packet that is to be sent, you must give not only the array of data and the length, but you must also supply the destination host and port number for the packet:
public DatagramPacket(byte[] buffer, int length, InetAddress destAddress, int destPortNumber)
When you create a datagram packet for receiving data, you only need to supply an array that is large enough to hold the incoming data, as well as the maximum number of bytes you wish to receive:
public DatagramPacket(byte[] buffer, int length)
The DatagramPacket class also provides methods to query the four components of the packet:
public InetAddress getAddress()
For an incoming datagram packet, getAddress returns the address that the datagram was sent from. For an outgoing packet, getAddress returns the address where the datagram will be sent.
public int getPort()
For an incoming datagram packet, this is the port number that the datagram was sent from. For an outgoing packet, this is the port number where the datagram will be sent.
public byte[] getData() public int getLength()
A datagram broadcast is the datagram equivalent of junk mail. It causes a packet to be sent to a number of hosts at the same time. When you broadcast, you always broadcast to a specific port number, but the network address you broadcast to is a special address.
Recall that Internet addresses are in the form a.b.c.d. Portions of this address are considered your host address, and other portions are considered your network address. The network address is the left portion of the address, while the host address is the right portion. The dividing line between them varies based on the first byte of the address (the a portion). If a is less than 128, the network address is just the a portion, while the b.c.d is your host address. This address is referred to as a Class A address. If a is greater than or equal to 128 and less than 192, the network address is a.b, and the host address is c.d. This address is referred to as a Class B address. If a is greater than or equal to 192, the network address is a.b.c, and the host address is d. This address is referred to as a Class C address.
Why is the network address important? If you want to be polite, you should only broadcast to your local network. Broadcasting to the entire world is rather rude and probably won't work anyway, since many routers block broadcasts past the local network. To send a broadcast to your local network, use the numeric address of the network and put in 255 for the portions that represent the host address. For example, if you are connected to Netcom, which has a network address that starts with 192, you should only broadcast Netcom's network of 192.100.81, which means the destination address for your datagrams should be 192.100.81.255. On the other hand, you might be on a network such as 159.165, which is a Class B address. On that network, you would broadcast to 159.165.255.255. You should consult your local system administrator about this, however, because many Class A and Class B networks are locally subdivided. You are safest just broadcasting to a.b.c.255 if you must broadcast at all.
Listing 34.6 shows a simple datagram server program that simply echoes back any datagrams it receives.
import java.net.*; /** * This is a simple datagram echo server that receives datagrams * and echoes them back untouched. */ public class DatagramServer extends Object { public static void main(String[] args) { try { // Create the datagram socket with a specific port number DatagramSocket mysock = new DatagramSocket(5432); // Allow packets up to 1024 bytes long byte[] buf = new byte[1024]; // Create the packet for receiving datagrams DatagramPacket p = new DatagramPacket(buf, buf.length); while (true) { // Read in the datagram mysock.receive(p); System.out.println("Received datagram!"); // A nice feature of datagram packets is that there is only one // address field. The incoming address and outgoing address are // really the same address. This means that when you receive // a datagram, if you want to send it back to the originating // address, you can just invoke send again. mysock.send(p); } } catch (Exception e) { e.printStackTrace(); } } }
Listing 34.7 shows a simple client that sends datagrams to the server and waits for a reply. If the datagrams get lost, however, this program will hang because it does not resend datagrams.
import java.net.*; /** * This program sends a datagram to the server every 2 seconds and waits * for a reply. If the datagram gets lost, this program will hang since it * has no retry logic. */ public class DatagramClient extends Object { public static void main(String[] args) { try { // Create the socket for sending DatagramSocket mysock = new DatagramSocket(); // Create the send buffer byte[] buf = new byte[1024]; // Create a packet to send. Currently just tries to send to the local host. // Change the inet address to make it send somewhere else. DatagramPacket p = new DatagramPacket(buf, buf.length, InetAddress.getLocalHost(), 5432); while (true) { // Send the datagram mysock.send(p); System.out.println("Client sent datagram!"); // Wait for a reply mysock.receive(p); System.out.println("Client received datagram!"); Thread.sleep(2000); } } catch (Exception e) { e.printStackTrace(); } } }
IP multicasting is a fairly new technology that represents an improvement over simple broadcasting. A multicast functions like a broadcast in that a single message gets sent to multiple recipients, but it is only sent to recipients that are looking for it.
The idea behind multicasting is that a certain set of network addresses are set aside as being multicast addresses. These addresses are in the range 225.0.0.0 to 239.255.255.255.
NOTE: Actually, network addresses between 224.0.0.0 and 224.255.255.255 are also IP multicast addresses, but they are reserved for non-application uses.
In order to send or receive multicast data, you must first create a multicast socket. A multi- cast socket is similar to a datagram socket (in fact, MulticastSocket is a subclass of DatagramSocket). You can create the multicast socket with a default port number or you can specify the port number in the constructor:
public MulticastSocket() throws IOException public MulticastSocket(int portNumber) throws IOException
To join a multicast address, use the joinGroup method; to leave a group, use the leaveGroup method:
public void joinGroup(InetAddress multicastAddr) throws IOException public void leaveGroup(InetAddress multicastAddr) throws IOException
On certain systems, you may have multiple network interfaces. This can cause a problem for multicasting because you need to listen on a specific interface. You can choose which interface your multicast socket uses by calling setInterface:
public void setInterface(InetAddress interface) throws SocketException
For example, if your machine had IP addresses of 192.0.0.1 and 193.0.1.15 and you wanted to listen for multicast messages on the 193 network, you would set your interface to the 193.0.1.15 address. Of course, you need to know the host name for that interface. You might have host names of myhost_neta for the 192 network and myhost_netb for the 193 network. In this case, you would set your interface this way:
mysocket.setInterface(InetAddress.getByName("myhost_netb"));
You can query the interface for a multicast socket by calling getInterface:
public InetAddress getInterface() throws SocketException
The key to multicast broadcasting is that you must send your packets out with a "time to live" value (also called TTL). This value indicates how far the packet should go (how many networks it should jump to). A TTL value of 0 indicates that the packet should stay on the local host. A TTL value of 1 indicates that the packet should only be sent on the local network. After that, the TTL values have more nebulous meanings. A TTL value of 32 means that the packet should only be sent to networks at this site. A TTL value of 64 means the packet should remain within this region, while a value of 128 means it should remain within this continent. A value of 255 means that the packet should go everywhere. Like broadcast datagrams, it is considered rude to send your packets to everyone. Try to limit the scope of your packets to the local network or, at least, the local site.
When you send a multicast datagram, you use a special version of the send method that takes a TTL value (if you use the default send method, the TTL is always 1):
public synchronized void send(DatagramPacket packet, byte timeToLive) throws IOException
You should also bear in mind that untrusted applets are not allowed to create MulticastSocket objects.