A discussion of the API for the Internet's premier programming language would not be complete without a look at the class library that makes networking possible-java.net. Although java.net is the final class library in the original Java API, new class libraries that extend the functionality of the Java programming language are continually being introduced. One such class library is the sun.tools.debug library, which was added to give Java built-in debugging support. Whenever you use the Java debugger, you are accessing this library.
The Net class library, java.net, provides a high-level interface for connections over the Internet or any other kind of TCP/IP network. Using these interfaces makes it possible for a Java application to connect to existing network services, provide its own network services, and even allow for multiuser, network-aware games over the Internet. Whereas these can be quite complicated to program in other languages, the java.net package allows the basics of socket connection to be set up in as little as 10 lines of code. That's power!
The following sections describe the classes in the java.net package.
ContentHandler is used to read a stream of data that is generated by a network connection and produce an object. The exact content handler called depends on the MIME type of the remote data.
ContentHandler should not be called directly. This task is much better performed by URL.getContent() or URLConnection.getContent(), which simplify the process of reading a stream of data.
DatagramPacket is used to both send and receive datagram packets. A datagram packet is a segment of data containing packet data, packet length, Internet address, and port. This allows programming at the IP packet level.
DatagramPacket methods are used to determine the address, length, and port of the datagram packet, as well as retrieve the data itself. See the online documentation for details.
DatagramSocket is used for creating UDP-based connections over a network. Services such as Sun's Network File System (NFS) use UDP as the underlying protocol. Java applications can also create and/or use UDP-based network connections by making use of this class. Only the advanced programmer will use this class. See the online documentation for details.
InetAddress makes an object of Internet addresses. This makes them able to be manipulated as String objects or within other network classes.
Listing 13.1 is an example of using the InetAddress class.
Listing 13.1. An example of class InetAddress.
/* Notes:
The main example simply accepts a hostname as an argument and
prints out the following information.
Hostname and IP address of system application is running on.
IP address of hostname
Additional IP addresses associated with hostname
(try www.microsoft.com)
Syntax:
MyMain <hostname>
*/
import java.net.*;
public class MyMain {
public static void main (String args[]) {
InetAddress inetAddr, inetAddrs[];
int i;
try {
inetAddr = InetAddress.getLocalHost();
System.out.println("Local System:");
System.out.println(" " + inetAddr);
// print out hostname and IP address of local host
inetAddr = InetAddress.getByName(args[0]);
// create an InetAddress object from a given hostname
System.out.println(inetAddr.getHostName() + ":");
System.out.println(" " + inetAddr);
// print out hostname and IP address in inetAddr
System.out.println("Additional addresses:");
inetAddrs = InetAddress.getAllByName(args[0]);
// get all addresses associated with a give hostname
for (i=1; i < inetAddrs.length; i++) {
// loop through printing out all addresses
System.out.println(" " + inetAddrs[i]);
}
}
catch (UnknownHostException e) {
// handle unknown host exceptions
System.err.println("ERROR: Hostname cannot be resolved");
System.err.println(" hostname = " + args[0]);
}
}
}
Java has classes within its java.net package to govern the use of sockets. Sockets are a network-specific utility. Those of you who are familiar with UNIX network programming are probably well versed in sockets.
Sockets are network processes that connect two hosts and create a two-way "pathway" to carry data between the server and the client. Sockets communicate on a specific port (but which port is up to the programmer). The application notifies the operating system that it is listening for activity on a certain port. The application then goes into a waiting state until it is requested to wake up due to activity on the port. It is not necessary for it to constantly query the port; the operating system notifies the application when there has been activity. The application then wakes up, completes the connection, and does whatever the programmer told it to do.
Sockets are useful in Java for implementing communication streams between programs. Sockets are the primary method of communication on the Internet. Web pages, file transfers, and electronic mail all communicate with each other using sockets. Because of the Java socket interface, it is easy to create Java applications that can participate in this network connectivity.
Java uses three classes in java.net for sockets: ServerSocket, Socket, and SocketImpl. The first two are the classes most likely to be used by a programmer. The last, SocketImpl, is generally implemented by the Java-enabled operating system. In other words, most programmers will not have to delve into the inner workings of SocketImpl.
ServerSocket is the socket created for the server side. ServerSocket listens for a connection request from a client and accepts the connection for a port on the server side.
Two of the methods associated with ServerSocket are accept and close. accept is used to create the socket and attach it to a port on the server end. close is used to shut down the connection at the end of its useful life. It is best to manually shut down the socket as opposed to waiting for the Java garbage collector to shut it down.
Listing 13.2 shows the source code for a generic socket server.
Listing 13.2. A Java-based generic socket server or daemon.
/* Notes:
This example implements a java based generic socket server or daemon.
It uses a socket to communicate with a socket client It can be used as
the basis for almost any type of socker server based application. This
particular example includes logging of the connections accepted and
will accept a filename as part of the constructor which will be used
for this logging capability.
To better demonstrate it's use, a prototype of a POP server has been
implemented. The POP server does not actually retrieve mail, but
does accept a connection on port 110 and will respond to a quit
command.
The main program simply creates a new JPop object and then executes
its start method. JPop will then loop waiting for and processing
connections. MyMain accepts a filename as an command line arguement.
This is used for logging of the connections.
Syntax:
MyMain <logfile name>
*/
import java.io.*;
import java.net.*;
import java.util.*;
public class MyMain {
public static void main (String args[]) {
int i;
JPop jp; // declare JPop object
jp = new JPop(args[0]); // create JPop instance
jp.start(); // starts the JPop daemon
}
}
class Daemon { // declare Daemon class
public DataInputStream is; // declare input stream variable
public PrintStream os; // declare output stream variable
public String remoteHost; // declare variable to hold remote hostname
Socket s; // declare socket object
ServerSocket ss; // declare server socket object
PrintStream lf; // declare printstream object
String logFile; // declare logfile name variable
int port; // declare port number variable
String name; // declare application name variable
// constructor for class Daemon
public Daemon(int port, String name, String logFile) {
this.port = port; // save port number
this.name = name; // save application name
this.logFile = logFile; // save logfile name
try {
lf = new PrintStream(new FileOutputStream(logFile)); // open logfile
}
// handle file not found exceptions
catch(FileNotFoundException e) {
System.err.println("ERROR: File not found");
System.err.println(" File = " + logFile);
System.exit(5);
}
catch(IOException e) {System.exit(6);} // handle IO exceptions
writeLogEnt("daemon started"); // write daemon started log entry
}
void start() { // method to start daemon
try {
ss = new ServerSocket(port); // create server socket
s = ss.accept(); // wait for incoming connection
// get name of remote host for logging
remoteHost = (s.getInetAddress()).getHostName();
// write log entry
writeLogEnt("connection accepted: port = " + s.getLocalPort() + ",
Âhost = " + remoteHost);
// open a data input stream using the socket getInputStream method
is = new DataInputStream(s.getInputStream());
// open an output PrintStream using the socket getOutputStream method
os = new PrintStream(s.getOutputStream());
}
catch(IOException e) { // handle exceptions
System.err.println("ERROR: IOexception(1) encountered");
this.exit(7);
}
}
void stop() { // declare stop method
writeLogEnt("daemon stopped"); // log daemon stopped message
try {
if (is != null) // test input stream is not null
is.close(); // explicitly close input stream
if (os != null) { // test if output stream is not null
os.flush(); // explicitly flush output stream
os.close(); // explicitly close output stream
}
if (s != null) // test if socket is null
s.close(); // explicitly close socket
}
catch(IOException e) { // handle exceptions
System.err.println("ERROR: IOexception(2) encountered");
this.exit(8);
}
}
String date() { // declare method for getting todays date
Date d = new Date();
return(d.getMonth()+"/"+d.getDate()+" "+d.getHours()+":
Â"+d.getMinutes()+":"+d.getSeconds());
}
void writeLogEnt(String entry) { // delcare method for writing log entries
lf.println(date() + " " + name + ": " + entry);
}
void exit(int errCode) { // declare common exit method
// log exit message
writeLogEnt("daemon exited with error code " + errCode);
if (s != null) { // test socket is not null
try {s.close();} // close socket
catch(IOException e) {
System.err.println("ERROR: IOexception(3) encountered");
}
}
System.exit(errCode); // exit application
}
}
class JPop { // declare Java POP class
Daemon d; // declare Daemon object
public JPop(String logFile) { // constructor for class JPop
d = new Daemon(110, "JPop", logFile); // create new Daemon instance
}
void start() { // declare start method
int i;
String resp; // variable to hold response from client
while (true) { // loop forever handling incoming connections
d.start(); // start daemon
// send response to remote host
write("+OK " + d.remoteHost + " JPOP server ready");
procConnection(); // process POP connection
}
}
// declare method to process POP connecitons
public void procConnection() {
// variable to hold response from client
String resp;
String cmd = "";
// process commands until quit received
while (! cmd.equalsIgnoreCase("quit")) {
resp = read(); // read response from client
// tokenize client response for processing
StringTokenizer st = new StringTokenizer(resp);
cmd = st.nextToken(); // get command
if (cmd.equalsIgnoreCase("quit")) { // test for quit command
// send server shutdown message to client
write("+OK " + d.remoteHost + " JPOP server shutdown");
d.stop(); // stop connection to client
continue; // go to top of loop
}
// send error message to client
write("-ERR Invalid command; valid commands: QUIT");
}
}
void write(String cmd) { // declare method to write string to client
d.os.println(cmd); // send string to client
}
String read() { // declare method to read string from client
String resp = "";
try {resp = d.is.readLine();} // read response from client
catch(IOException e) { // handle exceptions
System.err.println("ERROR: read IOException");
this.exit(10);
}
return(resp); // return exit code
}
void exit(int errCode) { // declare exit method
d.stop(); // close connection to client
System.exit(errCode); // exit application
}
}
When you start this program, on the command line, you must pass the name of the file that you want to log socket messages as an argument. You can do this as follows:
java MyMain socket.txt
In the example, the filename socket.txt is the name of the file that will log socket messages. While the program is running, you can test the socket connection and logging. To do this, start your browser and point to the IP address of your local system using port 110. If you are on a network-capable machine such as UNIX or Windows NT, you can use the local loopback IP address of 127.0.0.1 for testing. For example, the URL http://127.0.0.1:110 could be used to test port 110.
If you are able to start the program and test the socket connection, the text file will have entries similar to the following:
daemon started
connection accepted: port = 110,host = ppp-5.ts-4.dc.idt.net
Socket is the class used to create network connections and processes. Socket is used in conjunction with SocketImpl to implement the actual socket operations. Socket and SocketImpl are separated so that the implementation can be changed depending on the kind of firewall being used. See Listing 13.3 for an example of using Socket.
Listing 13.3. A Socket usage example.
/* Notes:
This example implements a java based version of the Unix "biff" utility.
It uses a socket to communicate with a POP server to determine if there
is any mail for the user.
The main program simply calls the JBiff class methods every 60 seconds
to determine if there is new mail present on the server or not. It
accepts three command line arguments specifying the host to connect
to, the username to use, and the password to use Note that this
is intended as an example only. Specifying passwords on command
lines is never recommended. To make this fully functional, a separate
dialogue box should be implemented to prompt for the password.
That is beyond the scope of this chapter however.
Syntax:
MyMain <hostname> <username> <password>
Historical note:
Where does "biff" come from? A program for checking mail was
written many years ago for Unix systems. The programmer
decided to simply name it after his or her dog "biff". Many other
"biff" type programs have been written since then and many
of these have carried on the historical fun of the "biff"
name. For this reason, the class implementing the "biff"
functionality was called jbiff for Java biff. On another
note, I had thought about naming the class after one
of our dogs, spot or buffy, but decided that a program that
was constantly giving out false alarms was not a good idea.
*/
import java.io.*;
import java.net.*;
import java.util.*;
public class MyMain {
public static void main (String args[]) {
int i;
JBiff jb; // declare jbiff object
// create jbiff instance
jb = new JBiff(args[0], args[1], args[2]);
// loop forever checking mail
while(true) {
// check for new mail
if (jb.chkMail()) {
// print out have NEW mail message
System.out.println("You have NEW mail");
}
else {
// print out have mail message
System.out.println("You have mail");
}
try {Thread.sleep(60000);} // sleep for 60 seconds
catch(InterruptedException e){}; // handle exceptions
}
}
}
class JBiff {
Socket s; // declare socket object
DataInputStream dis; // declare data input stream object
PrintStream dos; // declare printstream object
// variables for hostname, username, and password
String hostname, user, pass;
int mailBoxCnt; // variable to hold last count of mail messages
boolean mailBoxFlg = false; // variable to hold status of New mail flag
// constructor for class jbiff
public JBiff(String hostname, String user, String pass) {
mailBoxCnt = 0; // initialize mail message count to zero
this.hostname = hostname; // save hostname
this.user = user; // save username
this.pass = pass; // save password
}
// declare method for open socket connection to pop server
void openMailBox() {
String resp; // variable to hold response from server
try {
// open new socket connection to hostname on port 110
s = new Socket(hostname, 110);
// open a data input stream using the socket getInputStream method
dis = new DataInputStream(s.getInputStream());
// open an output PrintStream using the socket getOutputStream method
dos = new PrintStream(s.getOutputStream());
}
// handle file not found errors
catch(UnknownHostException e) {
System.err.println("ERROR: Host not found");
System.err.println(" Host = " + hostname);
System.exit(2);
}
catch(IOException e){}; // catch general IO exceptions
resp = read(); // read first line from pop server
resp = write("USER " + user); // send username command to pop server
resp = write("PASS " + pass); // send password command to pop server
}
// declare method to check for mail on pop server
public boolean chkMail() {
// variable to hold response from server
String resp;
// variable to hold current mail message count on server
int cnt;
// return code for method
boolean rcode;
// call method to open new connection to pop server
openMailBox();
// use STAT command to get count of mail messages on server
resp = write("STAT");
// tokenize server response for processing
StringTokenizer st = new StringTokenizer(resp);
// skip leading OK/ERR token
st.nextToken();
// get count of messages on server
cnt = (new Integer(st.nextToken())).intValue();
// send QUIT to close connection to pop server
write("QUIT");
// if more mail messages since last time, set new mail flag
if (cnt > mailBoxCnt) {
mailBoxFlg = true;
}
// if fewer mail messages since last time, clear new mail flag
if (cnt < mailBoxCnt) {
mailBoxFlg = false;
}
mailBoxCnt = cnt; // save number of messages for next comparison
return(mailBoxFlg); // return status of new mail flag
}
String write(String cmd) { // declare method to write string to pop server
dos.println(cmd); // send string command to pop server
return(read()); // return string returned from read of pop server
}
String read() { // declare method to read string from pop server
String resp = ""; // initialize response variable
try {
resp = dis.readLine(); // read response from pop server
if (! resp.startsWith("+OK")) { // test for acceptable response
// report error returned by pop server
System.err.println("ERROR: err returned by pop server");
System.err.println(" resp = " + resp);
System.exit(7);
}
}
catch(IOException e) { // handle exceptions
System.err.println("ERROR: read socket error");
System.exit(6);
}
return(resp); // return string read from pop server
}
}
Note |
If you cannot access your host using the IP address, try the fully qualified domain and hostname, such as www.tvp.com. |
SocketImpl is an abstract class, which means the programmer must subclass it to make it work. SocketImpl is used in conjunction with Socket to successfully adapt to different environments. One of the main reasons this is an abstract class is that the actual socket communications via Java are platform or firewall specific.
SocketImpl can be used with either streams or datagrams. It contains methods that can set a boolean as to whether the socket data is in a stream or a datagram.
The class defines other methods that accept, connect, close, and bind sockets to a port. These methods manipulate and control the socket. Of course, because they are part of an abstract class, the programmer must fill in the necessary details like IP address, port, and hostname.
Java has classes in the java.net package that allow manipulation and connection to other locations on the Internet. These locations are denoted by a unique Uniform Reference Locator (URL).
The URL is set up to perform certain activities when a connection is made to it, such as loading a Web page or downloading a file. You can include these interfaces in a Java applet or application.
These URL classes are similar to sockets in that they allow easy integration of network applications into a program. URLs are a bit more user-friendly to work with than are sockets.
Note |
Many of the examples in Chapter 10, "The java.lang and java.applet Class Libraries," use URLs to access files. Check out Listing 10.3, which shows a program that reads an audio file based on a URL. |
The URL class transforms a URL string into an object that can then be manipulated using associated methods. These methods serve purposes such as getting the filename, protocol, and hostname.
URL also has methods that can open and maintain the data connection of an input stream. This is much easier to use than the socket classes.
URLConnection is another abstract class used to create and control a connection to a platform- and firewall-specific location. It is a simplified version of connection interface for the URL class. See the online documentation for a full list of its methods.
URLEncoder takes a string of text and turns it into the into x-www-form-urlencoded format, which is a MIME format. This can then be used in conjunction with the URL class.
URLStreamHandler is an abstract class. Its purpose is to create a format for opening a stream connection to a specific URL. This class is concerned with the protocol of the URL. If a programmer wants to create a new URL stream protocol, it is necessary to implement the methods specified in this abstract class. This is most definitely only for advanced programmers.
The sun.tools.debug library provides the necessary framework for debugging Java programs. Whenever you use the Java debugger, you are accessing this library. If you accessed its classes directly, you could create your own debugger. Because most Java developers will not need to create their own debugger, this section provides only an overview of the classes the library contains to give you insight into the debugging process.
The DebuggerCallback interface is a template for implementing communications between an application and a debugger. The basic syntax for the interface is as follows:
public interface DebuggerCallback {
}
All communications between client applications and a debugger are asynchronous. Asynchronous communications are rather like the way you and I use e-mail: We send a message and the receiver responds to the message when he gets a chance. The sections that follow look at the abstract methods in the DebuggerCallback interface.
One of the key methods in this interface is breakpointEvent(), which is used to report when a breakpoint has been reached. The method accepts a RemoteThread object-the thread that reached the breakpoint-as a parameter. You use breakpoints during advanced debugging to help determine the behavior and state of the program at a certain point in the program's execution.
See Chapter 23, "Advanced Debugging and Troubleshooting" for more information on breakpoints.
Other key methods in this class include exceptionEvent(), printToConsole(), quitEvent(), and threadDeathEvent(). The exceptionEvent() method is used to report that an exception has occurred. It accepts a RemoteThread object and a String object as parameters.
The printToConsole() method is used whenever the DebuggerCallback interface prints messages to the console-which, as you will see when you use the Java debugger, is quite often. The printToConsole() method accepts a String object as a parameter.
The quitEvent() method is used to tell the debugger that the client has exited. Generally, clients exit either by returning from the main thread or by calling System.exit().
The threadDeathEvent() is used to report that a thread has died. This method accepts a RemoteThread object, which is the thread that died, as a parameter.
As you might expect, most of the classes in a debugging library for an object-oriented programming language are designed to handle the debugging methods associated with objects. A complete listing of the types of remote objects handled by the debugger is as follows: arrays, booleans, bytes, characters, classes, doubles, fields, floats, integers, longs, shorts, strings, threads, thread groups, and variable values.
Each of these object types is handled in a separate class. These classes include
RemoteArray
RemoteBoolean
RemoteByte
RemoteChar
RemoteClass
RemoteDouble
RemoteField
RemoteFloat
RemoteInt
RemoteLong
RemoteShort
RemoteString
RemoteThread
RemoteThreadGroup
RemoteValue
Most of the methods used with specific objects types are similar to the methods used in the RemoteObject class. For example, the RemoteArray class is one of many classes in the sun.tools.debug package that allow remote debugging of specific object types. As you can probably tell by the name of the class, the RemoteArray class allows remote debugging of arrays.
What you probably cannot tell from the class name is that the RemoteArray class uses an extensive list of methods that handle every facet of debugging array objects. Still, the methods in the RemoteArray class are similar to the methods in the more generic RemoteObject class. Therefore, by examining the methods in the RemoteObject class, you can gain an understanding of most of the other classes in the sun.tools.debug package.
Table 13.1 is a summary of the methods in the RemoteObject
class.
Method | Purpose |
description() | Returns a description of the object |
finalize() | Performs this code when garbage collection is run |
getClass() | Returns the object's class |
getField() | Returns an instance variable |
getFields() | Returns the non-static fields of an object |
getFieldValue() | Returns the value of an object's instance variable |
getId() | Returns the ID of an object |
setField() | Sets an instance variable, specified by slot or name |
toString() | Returns the object as a string |
typeName() | Returns the object type |
Using the RemoteStackFrame class, the debugger can examine a suspended thread's stackframe. As a Java developer, you will often use the debugger to call the methods of this class.
The stackframe provides a frame of reference for everything
related to a thread's stack. You can think of it as a snapshot
of a thread's internals, including the thread's program counter,
local variables, referenced methods, referenced classes, and the
current line number of execution. The methods used to retrieve
this information follow a very basic syntax. For example, when
you call the getLineNumber()
method, the method returns the current line number of execution.
This and other methods in this class are shown in Table 13.2.
Method | Purpose |
getLineNumber() | Returns the current line number of execution |
getLocalVariable() | Returns a named stack variable in the current stackframe |
getLocalVariables() | Returns an array of all valid local variables and method arguments for the stackframe |
getMethodName() | Returns the method name that the stackframe references |
getpc() | Returns the program counter that the stackframe references |
getRemoteClass() | Returns the class that the stackframe references |
The RemoteStackVariable class is similar to the RemoteStackFrame class. Using this class, the caller can obtain information about a specific stack variable.
The class has three methods: getName(), getValue(), and inScope(). The getName() method is used to get the name of a variable on the stack. The getValue() method is used to get the value of a variable on the stack. The inScope method is used to tell whether the variable is accessible.
Because the RemoteDebugger class can instantiate a connection with the Java interpreter session being debugged, client applications use this class as an interface to the Java debugging classes. To create a client interface, you use a constructor. The RemoteDebugger class provides an overloaded constructor interface that allows for the two different ways the debugger can be instantiated.
The first constructor is used to attach the debugger to a current interpreter session. As you will learn in Chapter 23, you do this by first starting the Java interpreter with the -debug option. The interpreter passes back a password, which is then passed to the debugger when it is invoked. In addition to accepting the password as a parameter, the constructor accepts a String object that identifies
Here is the syntax for the first constructor:
public RemoteDebugger(String host,
String password,
DebuggerCallback client,
boolean verbose) throws Exception
The second constructor is used to create a remote debugger that will start a client interpreter session. When you start the debugger directly, it in turn starts the Java interpreter with any parameters you passed on the command line. For this reason, the second constructor accepts arguments that are to be passed on to the interpreter. Other parameters allow the client using the DebuggerCallback interface to receive messages from the debugger, and others set a verbose flag to turn on or off internal debugger message text.
The syntax for the second constructor follows:
public RemoteDebugger(String javaArgs,
DebuggerCallback client,
boolean verbose) throws Exception
Methods in the RemoteDebugger
class enable debugging techniques you will use when you debug
your programs. These methods are shown in Table 13.3.
Method | Purpose |
addSystemThread() | Adds a system thread |
close() | Closes the connection to the remote debugger |
findClass() | Finds a specified class |
freeMemory() | Gets a report of the free memory available to the Java interpreter being debugged |
gc() | Frees all objects referenced by the debugger |
get() | Gets an object from the remote object cache |
getExceptionCatchList() | Gets the list of the exceptions on which the debugger will stop |
getSourcePath() | Gets the source file path the client is currently using |
itrace() | Turns instruction tracing on or off |
listBreakpoints() | Gets a list of the current breakpoints |
listClasses() | Gets a list of the currently known classes |
listThreadGroups() | Gets a list of the currently known thread groups |
run() | Loads and runs a runnable Java class |
setSourcePath() | Sets the search path for source files |
totalMemory() | Obtains a report of the total memory used by the Java interpreter being debugged |
trace(boolean) | Turns method call tracing on or off |
The StackFrame class provides a wrapper for the stackframe of a suspended thread. This class has a very basic constructor and a single method, called toString(), that returns an object as a string.
The java.net package contains classes that provide an easy-to-use interface to basic network programming. By using these classes, you can enable your Java application or applet to participate in the world of network connectivity. Some classes provide an abstract framework for protocol-specific programming. These abstract classes may be implemented in the Java-enabled operating-system interface.
The sun.tools.debug class library adds built-in debugging support to the Java programming language. Examining the classes in this library should have given you insight into the debugging process.
The mail server and mail client examples in this chapter are good demonstrations of how easy it can be to write complex network programs in Java, enabling even novice programmers to develop working network applications. This is yet another example of the power that has been built into Java, making it the programming language to use today!