Internet Programming with Java Course

2.3.

Finding and Loading Data Files

Whenever an applet needs to load some data from a file that's specified with a relative URL (a URL that doesn't completely specify the file's location), the applet usually uses either the code base or the document base to form the complete URL. The code base, returned by the Applet getCodeBase method, is a URL that specifies the directory from which the applet's classes were loaded. The document base, returned by the Applet getDocumentBase method, specifies the directory of the HTML page that contains the applet.

Unless the <APPLET> tag specifies a code base, both the code base and document base refer to the same directory on the same server.

Data that the applet always needs, or needs to rely on as a backup, is usually specified relative to the code base. Data that the applet user specifies, often by using parameters, is usually specified relative to the document base.

Note: For security reasons, browsers limit the URLs from which untrusted applets can read. For example, most browsers don't allow untrusted applets to use ".." to get to directories above the code base or document base. Also, since untrusted applets can't read files except those on the applet's originating host, the document base isn't generally useful if the document and the untrusted applet are on different servers.

The Applet class defines convenient forms of image-loading and sound-loading methods that let you specify images and sounds relative to a base URL. For example, assume an applet is set up with one of the directory structures shown in the following figure.

To create an Image object using the a.gif image file under imgDir, the applet can use the following code:

Image image = getImage(getCodeBase(), "imgDir/a.gif");

Working with Images Example

import java.awt.*;

import java.awt.event.*;

import java.applet.*;

import java.io.*;

 

public class ImageApplet extends Applet {

 

Image image;

Button button1 = new Button();

 

/**Construct the applet*/

public ImageApplet() {

}

/**Initialize the applet*/

public void init() {

try {

jbInit();

}

catch(Exception e) {

e.printStackTrace();

}

}

/**Component initialization*/

private void jbInit() throws Exception {

button1.setLabel("button1");

button1.setBounds(new Rectangle(50, 10, 75, 27));

button1.addActionListener(new java.awt.event.ActionListener() {

public void actionPerformed(ActionEvent e) {

button1_actionPerformed(e);

}

});

this.setLayout(null);

this.add(button1, null);

}

/**Get Applet information*/

public String getAppletInfo() {

return "Test Applet demonstrating a dynamic loading of images.";

}

 

/**

* We can run applet as Application.

*/

 

public static void main(String[] args) {

ImageApplet applet = new ImageApplet();

applet.isStandalone = true;

Frame frame;

frame = new Frame() {

protected void processWindowEvent(WindowEvent e) {

super.processWindowEvent(e);

if (e.getID() == WindowEvent.WINDOW_CLOSING) {

System.exit(0);

}

}

public synchronized void setTitle(String title) {

super.setTitle(title);

enableEvents(AWTEvent.WINDOW_EVENT_MASK);

}

};

frame.setTitle("Applet Frame");

frame.add(applet, BorderLayout.CENTER);

applet.init();

applet.start();

frame.setSize(800,600);

Dimension d = Toolkit.getDefaultToolkit().getScreenSize();

frame.setLocation((d.width - frame.getSize().width) / 2,

(d.height - frame.getSize().height) / 2);

frame.setVisible(true);

}

 

/**

* Method shows a scaled image.For detailed information see the API functions.

*/

public void paint(Graphics g) {

if (image!=null) { // check whether image is already loaded

Image image2 = image.getScaledInstance(50, 40, Image.SCALE_DEFAULT);

// function used to scale IMAGE

g.drawImage(image2, 50, 0, this); // function used to show IMAGE

}

}

 

/**

* Methods opens binary file and loads image from byte[]

*/

void button1_actionPerformed(ActionEvent e) {

byte[] buf=null;

try {

File file = new File("c:/niki.jpg");

int fileSize = (int)file.length();

System.out.println("Size: " + fileSize);

FileInputStream fin = new FileInputStream(file);

buf = new byte[fileSize];

int bytesRead = fin.read(buf, 0, fileSize);

} catch (IOException ioex) {

ioex.printStackTrace();

}

 

try {

System.out.println ("Image Showing...");

image = Toolkit.getDefaultToolkit().createImage(buf);

getGraphics().drawImage(image, 0, 0, this);

} catch (Exception ex1) {

ex1.printStackTrace();

}

}

}

Displaying Short Status Strings

All applet viewers -- from the Applet Viewer to Java-compatible browsers -- allow applets to display a short status string. In current implementations, this string appears on the status line at the bottom of the applet viewer window. In browsers, all applets on the page, as well as the browser itself, generally share the same status line.

You should never put crucial information in the status line. If many users might need the information, it should instead be displayed within the applet area. If only a few, sophisticated users might need the information, consider displaying the information on the standard output.

The status line is not usually very prominent, and it can be overwritten by other applets or by the browser. For these reasons, it's best used for incidental, transitory information. For example, an applet that loads several image files might display the name of the image file it's currently loading.

Applets display status lines with the showStatus method. Here's an example of its use:

showStatus("MyApplet: Loading image file " + file);

Note: Please don't put scrolling text in the status line. Browser users find such status line abuse highly annoying!

Displaying Documents in the Browser

Have you ever wanted an applet to display formatted HTML text? Here's the easy way to do it: Ask the browser to display the text for you.

With the AppletContext showDocument methods, an applet can tell the browser which URL to show and in which browser window. (By the way, the JDK Applet Viewer ignores these methods, since it can't display documents.) Here are the two forms of showDocument:

publicvoid showDocument(java.net.URL url)

publicvoid showDocument(java.net.URL url, String targetWindow)

The one-argument form of showDocument simply tells the browser to display the document at the specified URL, without specifying the window to display the document in.

Terminology Note: In this discussion, frame refers not to an AWT Frame but to an HTML frame within a browser window.

The two-argument form of showDocument lets you specify which window or HTML frame to display the document in. The second argument can have the values listed below.

"_blank" Display the document in a new, nameless window.

"windowName" Display the document in a window named windowName. This window is created if necessary.

"_self" Display the document in the window and frame that contain the applet.

"_parent" Display the document in the applet's window but in the parent frame of the applet's frame. If the applet frame has no parent frame, this acts the same as "_self".

"_top" Display the document in the applet's window but in the top-level frame. If the applet's frame is the top-level frame, this acts the same as "_self".

The following applet lets you try every option of both forms of showDocument. The applet brings up a window that lets you type in a URL and choose any of the showDocument options. When you press Return or click the Show document button, the applet calls showDocument.

import java.applet.*;

import java.awt.*;

import java.awt.event.*;

import java.net.URL;

import java.net.MalformedURLException;

 

public class ShowDocument extends Applet

implements ActionListener {

URLWindow urlWindow;

 

public void init() {

Button button = new Button("Bring up URL window");

button.addActionListener(this);

add(button);

 

urlWindow = new URLWindow(getAppletContext());

urlWindow.pack();

}

 

public void destroy() {

urlWindow.setVisible(false);

urlWindow = null;

}

 

public void actionPerformed(ActionEvent event) {

urlWindow.setVisible(true);

}

}

 

class URLWindow extends Frame

implements ActionListener {

TextField urlField;

Choice choice;

AppletContext appletContext;

 

public URLWindow(AppletContext appletContext) {

super("Show a Document!");

 

this.appletContext = appletContext;

 

GridBagLayout gridBag = new GridBagLayout();

GridBagConstraints c = new GridBagConstraints();

setLayout(gridBag);

 

Label label1 = new Label("URL of document to show:", Label.RIGHT);

gridBag.setConstraints(label1, c);

add(label1);

 

urlField = new TextField("http://java.sun.com/", 40);

urlField.addActionListener(this);

c.gridwidth = GridBagConstraints.REMAINDER;

c.fill = GridBagConstraints.HORIZONTAL;

c.weightx = 1.0;

gridBag.setConstraints(urlField, c);

add(urlField);

 

Label label2 = new Label("Window/frame to show it in:", Label.RIGHT);

c.gridwidth = 1;

c.weightx = 0.0;

gridBag.setConstraints(label2, c);

add(label2);

 

choice = new Choice();

choice.addItem("(browser's choice)"); //don't specify

choice.addItem("My Personal Window"); //a window named

//"My Personal Window"

choice.addItem("_blank"); //a new, unnamed window

choice.addItem("_self");

choice.addItem("_parent");

choice.addItem("_top"); //the Frame that contained this applet

c.fill = GridBagConstraints.NONE;

c.gridwidth = GridBagConstraints.REMAINDER;

c.anchor = GridBagConstraints.WEST;

gridBag.setConstraints(choice, c);

add(choice);

 

Button button = new Button("Show document");

button.addActionListener(this);

c.weighty = 1.0;

c.ipadx = 10;

c.ipady = 10;

c.insets = new Insets(5,0,0,0);

c.anchor = GridBagConstraints.SOUTH;

gridBag.setConstraints(button, c);

add(button);

 

addWindowListener(new WindowAdapter() {

public void windowClosing(WindowEvent event) {

setVisible(false);

}

});

}

public void actionPerformed(ActionEvent event) {

String urlString = urlField.getText();

URL url = null;

try {

url = new URL(urlString);

} catch (MalformedURLException e) {

System.err.println("Malformed URL: " + urlString);

}

 

if (url != null) {

if (choice.getSelectedIndex() == 0) {

appletContext.showDocument(url);

} else {

appletContext.showDocument(url,

choice.getSelectedItem());

}

}

}

}

Displaying Diagnostics to the Standard Output and Error Streams

Displaying diagnostics to the standard output can be an invaluable tool when you're debugging an applet. Another time you'll see messages at the standard output is when an uncaught exception occurs in an applet. Applets also have the option of using the standard error stream.

Where exactly the standard output and error are displayed varies, depending on how the applet's viewer is implemented, what platform it's running on, and (sometimes) how you launch the browser or applet viewer. When you launch the Applet Viewer from a UNIX shell window, for example, strings displayed to the standard output and error appear in that shell window, unless you redirect the output. When you launch the Applet Viewer from an X Windows menu, the standard output and error go to the console window. Netscape Navigator 2.0, on the other hand, always displays applet standard output and error to the Java Console, which is available from the Options menu.

Applets display to the standard output stream using System.out.print(String) and System.out.println(String). Displaying to the standard error stream is similar; just specify System.err instead of System.out. Here's an example of displaying to the standard output:

//Where instance variables are declared:

boolean DEBUG = true;

. . .

 

//Later, when we want to print some status:

if (DEBUG) {

System.out.println("Called someMethod(" + x + "," + y + ")");

}

Note: Displaying to the standard output and error streams is relatively slow. If you have a timing-related problem, printing messages to either of these streams might not be helpful.

You should be sure to disable all debugging output before you release your applet.

Security Restrictions

One of the main goals of the Java environment is to make browser users feel secure running any applet. To achieve this goal, we've started out conservatively, restricting capabilities perhaps more than necessary. As time passes, applets will probably get more and more abilities.

Each applet viewer has a SecurityManager object that checks for applet security violations. When a SecurityManager detects a violation, it creates and throws a SecurityException object. Generally, the SecurityException constructor prints a warning message to the standard output. An applet can catch SecurityExceptions and react appropriately, such as by reassuring the user and by resorting to a "safer" (but less ideal) way of accomplishing the task.

Some applet viewers swallow some SecurityExceptions, so that the applet never gets the SecurityException. For example, the JDK Applet Viewer's implementation of the AppletContext getApplet and getApplets methods simply catches and ignores any SecurityExceptions. The user can see an error message in the standard output, but at least the applet gets a valid result from the methods. This makes some sense, since getApplets should be able to return any valid applets it finds, even if it encounters invalid ones. (The Applet Viewer considers an applet valid if it's loaded from the same host as the applet that's calling getApplets.)

As the applet overview lesson mentioned, existing applet viewers (including Web browsers) impose the following restrictions:

Applets cannot load libraries or define native methods. Applets can use only their own Java code and the Java API the applet viewer provides. At a minimum, each applet viewer must provide access to the API defined in the java.* packages.

An applet cannot ordinarily read or write files on the host that is executing it. The JDK Applet Viewer actually permits some user-specified exceptions to this rule, but Netscape Navigator 2.0, for example, does not. Applets in any applet viewer can read files specified with full URLs, instead of by a filename. A workaround for not being to write files is to have the applet forward data to an application on the host the applet came from. This application can write the data files on its own host.

An applet cannot make network connections except to the host that it came from. The workaround for this restriction is to have the applet work with an application on the host it came from. The application can make its own connections anywhere on the network.

An applet cannot start any program on the host that is executing it. Again, an applet can work with a server-side application instead.

An applet cannot read certain system properties.

Windows that an applet brings up look different than windows that an application brings up. Applet windows have some warning text and either a colored bar or an image. This helps the user distinguish applet windows from those of trusted applications.

The following figures show a window brought up by a program that can run either as an applet or as an application. The first figure shows what the window looks like when the program is run as an application on the Solaris platform. The second figure shows the window when the program runs as an applet within the Solaris Netscape Navigator 2.0 browser.

As you can see, the applet window has a warning.

Working with a Server-Side Application

Applets, like other Java programs, can use the API defined in the java.net package to communicate across the network. The only difference is that, for security reasons, the only host an applet can communicate with is the host it was delivered from.

Note: Depending on the networking environment an applet is loaded into, and depending on the browser that runs the applet, an applet might not be able to communicate with its originating host. For example, browsers running on hosts inside firewalls often cannot get much information about the world outside the firewall. As a result, some browsers might not allow applet communication to hosts outside the firewall.

It's easy to find out which host an applet came from. Just use the Applet getCodeBase method and the java.net.URL getHost method, like this:

String host = getCodeBase().getHost();

Once you have the right host name, you can use all the networking code that you can use in a normal Java program.

Note: Not all browsers support all networking code flawlessly. For example, one widely used Java-compatible browser doesn't support posting to a URL.

A Simple Network Client Applet

The Writing a Datagram Client and Server section of the Custom Networking and Security trail contains example code for two applications, a client and a server. This section rewrites the client to be an applet. The client has been changed not only to communicate with the host the applet came from, but also to have a graphical UI, and to have a loop so that it can get as many quotes as you like. You can run the applet by including it in a page with the following HTML code:

<APPLET CODE=QuoteClientApplet.class WIDTH=500 HEIGHT=100>

</APPLET>

By saving this code to a file on your local HTTP server, you can use it to communicate with the server-side application that will be running on the HTTP server. You must also save the compiled form of the applet to the same directory.

Before the applet can get quotes, you need to run the server on the host that the applet came from. You then need to note the number of the port that the server is listening on. After you enter this port number into the applet, it will hook up with the server and you'll be able to get one-line quotations. Below are detailed instructions, followed by pictures of the server and the applet in action.

1. Compile QuoteServer.java and QuoteServerThread.java. A text file one-liners.txt should be in the same directory as the resulting class files.

2. On the computer that serves the applet class file (through HTTP), invoke the interpreter on the QuoteServer class. For example, if you view the applet's page with the URL http://mymachine/quoteApplet.html, then you need to run the server on the host named mymachine.

3. Record the port number that the quote server displays.

4. Enter this number into the applet's text field.

5. Press the Send button to request a quote from the server. You should see a quote appear in the text area.

Here's a picture of the applet in action:

/*

* Applet source code - for JDK 1.1.

*/

import java.applet.Applet;

import java.awt.*;

import java.awt.event.*;

import java.io.*;

import java.net.*;

import java.util.*;

 

public class QuoteClientApplet extends Applet

implements ActionListener {

boolean DEBUG = false;

InetAddress address;

TextField portField;

Label display;

DatagramSocket socket;

 

public void init() {

//Initialize networking stuff.

String host = getCodeBase().getHost();

 

try {

address = InetAddress.getByName(host);

} catch (UnknownHostException e) {

System.out.println("Couldn't get Internet address: Unknown host");

// What should we do?

}

 

try {

socket = new DatagramSocket();

} catch (IOException e) {

System.out.println("Couldn't create new DatagramSocket");

return;

}

 

//Set up the UI.

GridBagLayout gridBag = new GridBagLayout();

GridBagConstraints c = new GridBagConstraints();

setLayout(gridBag);

 

Label l1 = new Label("Quote of the Moment:", Label.CENTER);

c.anchor = GridBagConstraints.SOUTH;

c.gridwidth = GridBagConstraints.REMAINDER;

gridBag.setConstraints(l1, c);

add(l1);

 

display = new Label("(no quote received yet)", Label.CENTER);

c.anchor = GridBagConstraints.NORTH;

c.weightx = 1.0;

c.fill = GridBagConstraints.HORIZONTAL;

gridBag.setConstraints(display, c);

add(display);

 

Label l2 = new Label("Enter the port (on host " + host

+ ") to send the request to:",

Label.RIGHT);

c.anchor = GridBagConstraints.SOUTH;

c.gridwidth = 1;

c.weightx = 0.0;

c.weighty = 1.0;

c.fill = GridBagConstraints.NONE;

gridBag.setConstraints(l2, c);

add(l2);

 

portField = new TextField(6);

gridBag.setConstraints(portField, c);

add(portField);

 

Button button = new Button("Send");

gridBag.setConstraints(button, c);

add(button);

 

portField.addActionListener(this);

button.addActionListener(this);

}

 

public Insets getInsets() {

return new Insets(4,4,5,5);

}

 

public void paint(Graphics g) {

Dimension d = getSize();

Color bg = getBackground();

 

g.setColor(bg);

g.draw3DRect(0, 0, d.width - 1, d.height - 1, true);

g.draw3DRect(3, 3, d.width - 7, d.height - 7, false);

}

 

void doIt(int port) {

DatagramPacket packet;

byte[] sendBuf = new byte[256];

packet = new DatagramPacket(sendBuf, 256, address, port);

 

try { // send request

if (DEBUG) {

System.out.println("Applet about to send packet to address "

+ address + " at port " + port);

}

socket.send(packet);

if (DEBUG) {

System.out.println("Applet sent packet.");

}

} catch (IOException e) {

System.out.println("Applet socket.send failed:");

e.printStackTrace();

return;

}

 

packet = new DatagramPacket(sendBuf, 256);

 

try { // get response

if (DEBUG) {

System.out.println("Applet about to call socket.receive().");

}

socket.receive(packet);

if (DEBUG) {

System.out.println("Applet returned from socket.receive().");

}

} catch (IOException e) {

System.out.println("Applet socket.receive failed:");

e.printStackTrace();

return;

}

 

String received = new String(packet.getData());

if (DEBUG) {

System.out.println("Quote of the Moment: " + received);

}

display.setText(received);

}

 

public void actionPerformed(ActionEvent event) {

int port;

try {

port = Integer.parseInt(portField.getText());

doIt(port);

} catch (NumberFormatException e) {

//No integer entered. Should warn the user.

}

}

}

 

 

/*

* QuoteServer.java

*/

class QuoteServer {

public static void main(String[] args) {

new QuoteServerThread().start();

}

}

 

 

/*

* QuoteServerThread.java

*/

import java.io.*;

import java.net.*;

import java.util.*;

 

class QuoteServerThread extends Thread {

private DatagramSocket socket = null;

private BufferedReader qfs = null;

 

QuoteServerThread() {

super("QuoteServer");

try {

socket = new DatagramSocket();

System.out.println("QuoteServer listening on port: " + socket.getLocalPort());

} catch (java.io.IOException e) {

System.err.println("Could not create datagram socket.");

}

this.openInputFile();

}

 

public void run() {

if (socket == null)

return;

 

while (true) {

try {

byte[] buf = new byte[256];

DatagramPacket packet;

InetAddress address;

int port;

String dString = null;

 

// receive request

packet = new DatagramPacket(buf, 256);

socket.receive(packet);

address = packet.getAddress();

port = packet.getPort();

 

// send response

if (qfs == null)

dString = new Date().toString();

else

dString = getNextQuote();

buf = dString.getBytes();

packet = new DatagramPacket(buf, buf.length, address, port);

socket.send(packet);

} catch (IOException e) {

System.err.println("IOException: " + e);

e.printStackTrace();

}

}

}

 

protected void finalize() {

if (socket != null) {

socket.close();

socket = null;

System.out.println("Closing datagram socket.");

}

}

 

private void openInputFile() {

try {

qfs = new BufferedReader(new InputStreamReader(

new FileInputStream("one-liners.txt")));

} catch (java.io.FileNotFoundException e) {

System.err.println("Could not open quote file. Serving time instead.");

}

}

private String getNextQuote() {

String returnValue = null;

try {

if ((returnValue = qfs.readLine()) == null) {

qfs.close();

this.openInputFile();

returnValue = qfs.readLine(); // we know the file has at least one input line

}

} catch (IOException e) {

returnValue = "IOException occurred in server.";

}

return returnValue;

}

 

}

Using a Server to Work Around Security Restrictions

Applets are subject to many security restrictions. For example, they can't perform file I/O, they can't make network connections except to their original host, and they can't start programs.

One way of working around these restrictions is to use a server application that executes on the applet's host. The server won't be able to get around every applet restriction, but it can make more things possible. For example, a server probably can't save files on the host the applet's running on, but it'll be able to save files on the host the applet originated from.

This page features an example of a server that allows two applets to communicate with each other. The applets don't have to be running on the same page, in the same browser, or on the same computer. As long as the applets originate from the same computer, they can communicate through the server that's running on that originating computer.

Here are the source files:

/*

* TalkClientApplet.java

*/

import java.applet.Applet;

import java.awt.*;

import java.awt.event.*;

import java.io.*;

import java.net.*;

import java.util.*;

 

public class TalkClientApplet extends Applet

implements Runnable, ActionListener {

Socket socket;

BufferedWriter os;

BufferedReader is;

TextField portField, message;

TextArea display;

Button button;

int dataPort;

boolean trysted;

Thread receiveThread;

String host;

boolean DEBUG = false;

String newline;

 

public void init() {

//Get the address of the host we came from.

host = getCodeBase().getHost();

 

//Set up the UI.

GridBagLayout gridBag = new GridBagLayout();

GridBagConstraints c = new GridBagConstraints();

setLayout(gridBag);

 

message = new TextField("");

c.fill = GridBagConstraints.HORIZONTAL;

c.gridwidth = GridBagConstraints.REMAINDER;

gridBag.setConstraints(message, c);

message.addActionListener(this);

add(message);

 

display = new TextArea(10, 40);

display.setEditable(false);

c.weightx = 1.0;

c.weighty = 1.0;

c.fill = GridBagConstraints.BOTH;

gridBag.setConstraints(display, c);

add(display);

 

Label l = new Label("Enter the port (on host " + host

+ ") to send the request to:",

Label.RIGHT);

c.fill = GridBagConstraints.HORIZONTAL;

c.gridwidth = 1;

c.weightx = 0.0;

c.weighty = 0.0;

gridBag.setConstraints(l, c);

add(l);

 

portField = new TextField(6);

c.fill = GridBagConstraints.NONE;

gridBag.setConstraints(portField, c);

portField.addActionListener(this);

add(portField);

 

button = new Button("Connect");

gridBag.setConstraints(button, c);

button.addActionListener(this);

add(button);

 

newline = System.getProperty("line.separator");

}

 

public synchronized void start() {

if (DEBUG) {

System.out.println("In start() method.");

}

if (receiveThread == null) {

trysted = false;

portField.setEditable(true);

button.setEnabled(true);

os = null;

is = null;

socket = null;

receiveThread = new Thread(this);

receiveThread.start();

if (DEBUG) {

System.out.println(" Just set everything to null and started thread.");

}

} else if (DEBUG) {

System.out.println(" receiveThread not null! Did nothing!");

}

}

 

public synchronized void stop() {

if (DEBUG) {

System.out.println("In stop() method.");

}

receiveThread = null;

trysted = false;

portField.setEditable(true);

button.setEnabled(true);

notify();

 

try { //Close input stream.

if (is != null) {

is.close();

is = null;

}

} catch (Exception e) {} //Ignore exceptions.

 

try { //Close output stream.

if (os != null) {

os.close();

os = null;

}

} catch (Exception e) {} //Ignore exceptions.

 

try { //Close socket.

if (socket != null) {

socket.close();

socket = null;

}

} catch (Exception e) {} //Ignore exceptions.

}

public Insets getInsets() {

return new Insets(4,4,5,5);

}

 

public void paint(Graphics g) {

Dimension d = getSize();

Color bg = getBackground();

 

g.setColor(bg);

g.draw3DRect(0, 0, d.width - 1, d.height - 1, true);

g.draw3DRect(3, 3, d.width - 7, d.height - 7, false);

}

 

public synchronized void actionPerformed(ActionEvent event) {

int port;

if (DEBUG) {

System.out.println("In action() method.");

}

 

if (receiveThread == null) {

start();

}

 

if (!trysted) {

//We need to attempt a rendezvous.

 

if (DEBUG) {

System.out.println(" trysted = false. "

+ "About to attempt a rendezvous.");

}

 

//Get the port the user entered...

try {

port = Integer.parseInt(portField.getText());

} catch (NumberFormatException e) {

//No integer entered.

display.append("Please enter an integer below."

+ newline);

return;

}

//...and rendezvous with it.

rendezvous(port);

 

} else { //We've already rendezvoused. Just send data over.

if (DEBUG) {

System.out.println(" trysted = true. "

+ "About to send data.");

}

String str = message.getText();

message.selectAll();

 

try {

os.write(str);

os.newLine();

os.flush();

} catch (IOException e) {

display.append("ERROR: Applet couldn't write to socket."

+ newline);

display.append("...Disconnecting."

+ newline);

stop();

return;

} catch (NullPointerException e) {

display.append("ERROR: No output stream!"

+ newline);

display.append("...Disconnecting."

+ newline);

stop();

return;

}

display.append("Sent: " + str + newline);

}

}

 

synchronized void waitForTryst() {

//Wait for notify() call from action().

try {

wait();

} catch (InterruptedException e) {}

 

if (DEBUG) {

System.out.println("waitForTryst about to return. "

+ "trysted = " + trysted + ".");

}

 

return;

}

 

public void run() {

String received = null;

 

waitForTryst();

 

//OK, now we can send messages.

while (Thread.currentThread() == receiveThread) {

try {

//Wait for data from the server.

received = is.readLine();

 

//Display it.

if (received != null) {

display.append("Received: " + received

+ newline);

} else { //success but no data...

System.err.println("End of stream?");

return; //XXX

}

} catch (IOException e) { //Perhaps a temporary problem?

display.append("NOTE: Couldn't read from socket.\n");

return;

}

}

}

 

private void rendezvous(int port) {

//Try to open a socket to the port.

try {

socket = new Socket(host, port);

} catch (UnknownHostException e) {

display.append("ERROR: Can't find host: " + host

+ newline);

return;

} catch (IOException e) {

display.append("ERROR: Can't open socket on rendezvous port "

+ port + " (on host " + host + ")."

+ newline);

return;

}

 

//Try to open streams to read and write from the socket.

try {

os = new BufferedWriter(

new OutputStreamWriter(socket.getOutputStream()));

is = new BufferedReader(

new InputStreamReader(socket.getInputStream()));

} catch (IOException e) {

display.append("ERROR: Created data socket but can't "

+ "open stream on it."

+ newline);

display.append("...Disconnecting." + newline);

stop();

return;

}

 

if ((os != null) & (is != null)) {

if (DEBUG) {

System.out.println("Successful rendezvous.");

System.out.println("socket = " + socket);

System.out.println("output stream = " + os);

System.out.println("input stream = " + is);

}

//Let the main applet thread know we've successfully rendezvoused.

portField.setEditable(false);

button.setEnabled(false);

trysted = true;

notify();

} else {

display.append("ERROR: Port is valid but communication failed. "

+ "Please TRY AGAIN." + newline);

}

}

 

}

After you compile the code above, you can run it by including it in an HTML page with this tag:

<APPLET CODE=TalkClientApplet.class WIDTH=550 HEIGHT=200>
</applet>

After saving this page to a file on your local HTTP server, you can use it to communicate with the talk server TalkServer.java and TalkServerThread.java

/*

* TalkServer.java

*/

import java.net.*;

import java.io.*;

 

class TalkServer {

TalkServerThread[] tstList = new TalkServerThread[2];

boolean DEBUG = false;

 

public static void main(String[] args) {

new TalkServer().start();

}

 

public void start() {

ServerSocket serverRSocket = null;

int numConnected = 0;

 

try {

serverRSocket = new ServerSocket(0);

System.out.println("TalkServer listening on rendezvous port: "

+ serverRSocket.getLocalPort());

} catch (IOException e) {

System.err.println("Server could not create server socket for rendezvous.");

return;

}

 

while (true) {

 

//Connect to two clients.

while (numConnected < 2) {

TalkServerThread tst;

tst = connectToClient(serverRSocket);

if (tst != null) {

numConnected++;

if (tstList[0] == null) {

tstList[0] = tst;

} else {

tstList[1] = tst;

}

}

} //end while (numConnected < 2) loop

 

if (DEBUG) {

try {

System.out.println("tst #0 = " + tstList[0]);

} catch (Exception e) {}

try {

System.out.println("tst #1 = " + tstList[1]);

} catch (Exception e) {}

}

 

//If they're really OK, tell them to start writing.

if (everythingIsOK(0) & everythingIsOK(1)) {

for (int i = 0; i < 2; i++) {

writeToStream("START WRITING!\n----------------------"

+ "-------------", tstList[i].os);

}

} else {

System.err.println("2 server threads created, but not everything is OK");

}

 

while (numConnected == 2) {

if (!everythingIsOK(0)) {

if (DEBUG) {

System.out.println("Applet #0 is hosed; disconnecting.");

}

numConnected--;

cleanup(tstList[0]);

tstList[0] = null;

}

if (!everythingIsOK(1)) {

if (DEBUG) {

System.out.println("Applet #1 is hosed; disconnecting.");

}

numConnected--;

cleanup(tstList[1]);

tstList[1] = null;

}

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

}

} //end while(numConnected==2) loop

 

if (DEBUG) {

try {

System.out.println("Number of connections = " + numConnected);

System.out.println("tst #0 = " + tstList[0]);

System.out.println("tst #1 = " + tstList[1]);

} catch (Exception e) {}

}

 

} //end while (true) loop

}

 

protected TalkServerThread connectToClient(ServerSocket serverRSocket) {

Socket rendezvousSocket = null;

TalkServerThread tst = null;

 

//Listen for client connection on the rendezvous socket.

try {

rendezvousSocket = serverRSocket.accept();

} catch (IOException e) {

System.err.println("Accept failed.");

e.printStackTrace();

return null;

}

 

//Create a thread to handle this connection.

try {

tst = new TalkServerThread(rendezvousSocket, this);

tst.start();

} catch (Exception e) {

System.err.println("Couldn't create TalkServerThread:");

e.printStackTrace();

return null;

}

 

writeToStream("Successful connection. "

+ "Please wait for second applet to connect...", tst.os);

return tst;

}

 

boolean everythingIsOK(int tstNum) {

TalkServerThread tst = tstList[tstNum];

 

if (tst == null) {

if (DEBUG) {

System.out.println("TalkServerThread #" + tstNum

+ " is null");

}

return false;

} else {

if (tst.os == null) {

if (DEBUG) {

System.out.println("TalkServerThread #" + tstNum

+ " output stream is null.");

}

return false;

}

if (tst.is == null) {

if (DEBUG) {

System.out.println("TalkServerThread #" + tstNum

+ " input stream is null.");

}

return false;

}

if (tst.socket == null) {

if (DEBUG) {

System.out.println("TalkServerThread #" + tstNum

+ " socket is null.");

}

return false;

}

}

return true;

}

 

void cleanup(TalkServerThread tst) {

if (tst != null) {

try {

if (tst.os != null) {

tst.os.close();

tst.os = null;

}

} catch (Exception e) {} //Ignore errors

try {

if (tst.is != null) {

tst.is.close();

tst.is = null;

}

} catch (Exception e) {} //Ignore errors

try {

if (tst.socket != null) {

tst.socket.close();

tst.socket = null;

}

} catch (Exception e) {} //Ignore errors

}

}

 

public void forwardString(String string, TalkServerThread requestor) {

BufferedWriter clientStream = null;

 

if (tstList[0] == requestor) {

if (tstList[1] != null) {

clientStream = tstList[1].os;

} else {

if (DEBUG) {

System.out.println("Applet #0 has a string to forward, "

+ "but Applet #1 is gone...");

}

return;

}

} else {

if (tstList[0] != null) {

clientStream = tstList[0].os;

} else {

if (DEBUG) {

System.out.println("Applet #1 has a string to forward, "

+ "but Applet #0 is gone...");

}

return;

}

}

 

if (clientStream != null) {

writeToStream(string, clientStream);

} else if (DEBUG) {

System.out.println("Can't forward string -- no output stream.");

}

}

 

public void writeToStream(String string, BufferedWriter stream) {

if (DEBUG) {

System.out.println("TalkServer about to forward data: " + string);

}

 

try {

stream.write(string);

stream.newLine();

stream.flush();

if (DEBUG) {

System.out.println("TalkServer forwarded string.");

}

} catch (IOException e) {

System.err.println("TalkServer failed to forward string:");

e.printStackTrace();

return;

} catch (NullPointerException e) {

System.err.println("TalkServer can't forward string "

+ "since output stream is null.");

return;

}

}

}

 

 

/*

* TalkServerThread.java

*/

import java.io.*;

import java.net.*;

import java.util.*;

 

class TalkServerThread extends Thread {

public Socket socket;

public BufferedReader is;

public BufferedWriter os;

TalkServer server;

boolean DEBUG = false;

 

public String toString() {

return "TalkServerThread: socket = " + socket + "; is = " + is + "; os = " + os;

}

 

TalkServerThread(Socket socket, TalkServer server) throws IOException {

super("TalkServer");

 

is = new BufferedReader(

new InputStreamReader(socket.getInputStream()));

os = new BufferedWriter(

new OutputStreamWriter(socket.getOutputStream()));

 

if (is == null) {

System.err.println("TalkServerThread: Input stream seemed "

+ "to be created successfully, but it's null.");

throw new IOException();

}

 

if (os == null) {

System.err.println("TalkServerThread: Output stream seemed "

+ "to be created successfully, but it's null.");

throw new IOException();

}

 

this.socket = socket;

this.server = server;

}

 

public void run() {

while (socket != null) {

try {

//Read data.

String str = is.readLine();

//Pass it on.

if (str != null) {

server.forwardString(str, this);

}

} catch (EOFException e) { //No more data on this socket...

server.forwardString("SERVER SAYS other applet disconnected", this);

cleanup();

return;

} catch (NullPointerException e) { //Socket doesn't exist...

server.forwardString("SERVER SAYS no socket to other applet", this);

cleanup();

return;

} catch (IOException e) { //Read problem..

server.forwardString("SERVER SAYS socket trouble with other applet", this);

cleanup();

return;

} catch (Exception e) { //Unknown exception. Complain and quit.

System.err.println("Exception on is.readLine():");

e.printStackTrace();

cleanup();

return;

}

}

}

 

protected void finalize() {

cleanup();

}

 

void cleanup() {

try {

if (is != null) {

is.close();

is = null;

}

} catch (Exception e) {} //Ignore errors.

 

try {

if (os != null) {

os.close();

os = null;

}

} catch (Exception e) {} //Ignore errors.

 

try {

if (socket != null) {

socket.close();

socket = null;

}

} catch (Exception e) {} //Ignore errors.

}

}

 

After compiling both files, you can run the server on the applets' originating host by invoking the interpreter on the TalkServer class.

The instructions for running the server are just like those for the previous example. Run the server on the applets' originating host, recording the port number the applets should rendezvous on. then initialize both applets (which can be running on different machines) to talk to the server port number. After this initialization is complete, type into each applet and press the Return key to send the message to the other applet.

Here's the server in action:

www% java TalkServer
TalkServer listening on rendezvous port: 36567

Here are pictures of the applets in action: