- Special Edition Using Java, 2nd Edition -

Chapter 32

java.net


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

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 RFC1630. A 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 a 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 a 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.mcp.com/que”);

You can also create a 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.mcp.com”, “que”);

or

URL queHomePage = new URL(“http”, “www.mcp.com”, 80,
“que”); // 80 is default http port

If you have already created a 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 a URL for the directory where the applet’s .class file resides. You can also get a 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 a URL for myfile.txt with:

URL myfileURL = new URL(getCodeBase(), “myfile.txt”);

Getting URL Contents

Once you create a 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 a URLConnection for a URL with the openConnection method:

public URLConnection openConnection() throws IOException

Your third option for getting the contents of a 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 a 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
}

Getting URL Information

You can retrieve the specific pieces of a 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

The URLConnection class provides a more granular interface to a 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 a 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 a URLConnection object yourself; it is created and returned by a URL object. Once you have an instance of a 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 a 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 a 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 URLEncoder Class

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

The URLStreamHandler class is responsible for parsing a URL and creating a URLConnection object to access that URL. When you open a connection for a 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 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 a URLConnection that speaks the FTP protocol.

You can also change the way a 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 a 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 a 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);

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.
 

The ContentHandler Class

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 form

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 that 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

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:

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.

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 trade-off 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.
 

Sending and Receiving Socket Data

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

Getting Socket Information

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()

Closing the Socket Connection

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

Waiting for Incoming Data

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 32.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.

Listing 32.1 Source code for ReadThread.java

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 we 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 we have a string
readCallback.dataReady(str);
}
catch (Exception oops)
{
// Tell the callback there was an error
readCallback.dataReady(null);
}
}
}
}

Listing 32.2 shows the ReadCallback interface, which must be implemented by a class to receive data from a ReadThread object.

Listing 32.2 Source code for ReadCallback.java

/**
* 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);
}

A Simple Socket Client

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 32.3 shows the SimpleClient class.

Listing 32.3 Source code for SimpleClient.java

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-independant 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",
4321);
client.start();
} catch (Exception cantStart) {
System.out.println("Got error");
cantStart.printStackTrace();
}
}
}

The ServerSocket Class

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

Because of current security restrictions in Netscape and other browsers, you may not be able to accept socket connections with an applet.

Accepting Incoming Socket Connections

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.

Getting the Server Socket Address

If you need to find out 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 watch 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.

Writing a Server Program

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 the 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 32.4 shows an example client handler object that talks to an individual client and passes the client’s request up to the main server.

Listing 32.4 Source code for ServerConn.java.

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 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 return 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 32.5 shows a simple server that takes a string and sends back the reverse of the string.

Listing 32.5 Source code for SimpleServer.java


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 we 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

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.

Converting a Name to an Address

The InetAddress class handles all the intricacies of name look-up 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

Examining the InetAddress

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 AND-ed 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 left-most bit is 1, the number is negative. Internet addresses are not usually written with negative numbers. By AND-ing the values with 255, you do not change the value, but you suddenly treat the value as an 32-bit integer value whose left-most bit is 0, and whose right-most 8 bits represent the address.

Getting An Applets Originating 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

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 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 DatagramSocket is the network equivalent of a mailbox, then the DatagramPacket 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 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 receives 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 DatagramSocket 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

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 is used in two ways:

To create a DatagramPacket 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 DatagramPacket 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 also provides methods to query the four components of the packet:

public InetAddress getAddress()

For an incoming DatagramPacket, 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()

Broadcasting Datagrams

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-hand portion of the address, while the host address is the right-hand 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.

A Simple Datagram Server

Listing 32.6 shows a simple datagram server program that simply echoes back any datagrams it receives.

Listing 32.6 Source code for DatagramServer.java

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 32.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 hangbecause it does not resend datagrams.

Listing 32.7 Source code for DatagramClient.java

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();
}
}
}


Previous Page TOC Next Page

| Previous Chapter | Next Chapter |

|Table of Contents | Book Home Page |

| Que Home Page | Digital Bookshelf | Disclaimer |


To order books from QUE, call us at 800-716-0044 or 317-361-5400.

For comments or technical support for our books and software, select Talk to Us.

© 1996, QUE Corporation, an imprint of Macmillan Publishing USA, a Simon and Schuster Company