`
xuehu2010
  • 浏览: 4514 次
  • 性别: Icon_minigender_1
  • 来自: 西安
最近访客 更多访客>>
社区版块
存档分类
最新评论

applet与servlet通信经典实例

阅读更多

10.2. Daytime Server

For a simple demonstration of each communication technique, we're going to write an applet that asks its server for the current time of day. The applet first uses an HTTP connection, then a non-HTTP socket connection, and finally an RMI connection. Of course, an applet can normally get the current time from the system on which it's running. To give this example an air of practicality, let's assume the applet needs an approximate time stamp for some event and cannot rely on the client machine to have a correctly set clock.

10.2.1. The Applet

We're going to be using the same example applet throughout this section. The skeleton code for this applet, DaytimeApplet, is shown in Example 10-1. Right now, the applet just creates a user interface where the times it retrieves can be displayed, as shown in Figure 10-1. As we proceed with this example, we'll implement its getDateUsingHttpText(), getDateUsingHttpObject(), getDateUsingSocketText(), getDateUsingSocketObject(), and getDateUsingRMIObject() methods. Note that the examples in this chapter use several JDK 1.0 methods that are deprecated in JDK 1.1. This is to maximize portability.

 

 

 

 

Figure 10-1. The DaytimeApplet user interface

Example 10-1. DaytimeApplet, without all the good stuff

import java.applet.*;
import java.awt.*;
import java.io.*;
import java.util.*;

public class DaytimeApplet extends Applet {

  TextField httpText, httpObject, socketText, socketObject, RMIObject;
  Button refresh;

  public void init() {
    // Construct the user interface

    setLayout(new BorderLayout());

    // On the left create labels for the various communication
    // mechanisms
    Panel west = new Panel();
    west.setLayout(new GridLayout(5, 1));
    west.add(new Label("HTTP text: ", Label.RIGHT));
    west.add(new Label("HTTP object: ", Label.RIGHT));
    west.add(new Label("Socket text: ", Label.RIGHT));
    west.add(new Label("Socket object: ", Label.RIGHT));
    west.add(new Label("RMI object: ", Label.RIGHT));
    add("West", west);

    // On the right create text fields to display the retrieved time values
    Panel center = new Panel();
    center.setLayout(new GridLayout(5, 1));

    httpText = new TextField();
    httpText.setEditable(false);
    center.add(httpText);

    httpObject = new TextField();
    httpObject.setEditable(false);
    center.add(httpObject);

    socketText = new TextField();
    socketText.setEditable(false);
    center.add(socketText);

    socketObject = new TextField();
    socketObject.setEditable(false);
    center.add(socketObject);

    RMIObject = new TextField();
    RMIObject.setEditable(false);
    center.add(RMIObject);

    add("Center", center);

    // On the bottom create a button to update the times
    Panel south = new Panel();
    refresh = new Button("Refresh");
    south.add(refresh);
    add("South", south);
  }

  public void start() {
    refresh();
  }

  private void refresh() {
    // Fetch and display the time values
    httpText.setText(getDateUsingHttpText());
    httpObject.setText(getDateUsingHttpObject());
    socketText.setText(getDateUsingSocketText());
    socketObject.setText(getDateUsingSocketObject());
    RMIObject.setText(getDateUsingRMIObject());
  }

  private String getDateUsingHttpText() {
    // Retrieve the current time using an HTTP text-based connection
    return "unavailable";
  }

  private String getDateUsingHttpObject() {
    // Retrieve the current time using an HTTP object-based connection
    return "unavailable";
  }

  private String getDateUsingSocketText() {
    // Retrieve the current time using a non-HTTP text-based socket
    // connection
    return "unavailable";
  }

  private String getDateUsingSocketObject() {
    // Retrieve the current time using a non-HTTP object-based socket
    // connection
    return "unavailable";
  }

  private String getDateUsingRMIObject() {
    // Retrieve the current time using RMI communication
    return "unavailable";
  }

  public boolean handleEvent(Event event) {
    // When the refresh button is pushed, refresh the display
    // Use JDK 1.0 events for maximum portability
    switch (event.id) {
      case Event.ACTION_EVENT:
        if (event.target == refresh) {
          refresh();
          return true;
        }
    }
    return false;
  }
}

For this applet to be available for downloading to the client browser, it has to be placed under the server's document root, along with an HTML file referring to it. The HTML might look like this:

<HTML>
<HEAD><TITLE>Daytime Applet</TITLE></HEAD>
<BODY>
<CENTER><H1>Daytime Applet</H1></CENTER>
<CENTER><APPLET CODE=DaytimeApplet CODEBASE=/ WIDTH=300 HEIGHT=180>
</APPLET></CENTER>
</BODY></HTML>

The CODEBASE parameter indicates the directory where the applet's class file has been placed. The parameter is relative to the document root, which for the Java Web Server is generally server_root/public_html. Assuming the HTML file was named daytime.html, this applet can be viewed at the URL http://server:port/daytime.html.

10.2.2. Text-based HTTP Communication

Let's start by implementing the lowest-common-denominator approach-- text-based HTTP communication.

10.2.2.1. The servlet

For the DaytimeApplet to retrieve the current time from the server, it has to communicate with a servlet that returns the current time. Example 10-2 shows such a servlet. It responds to all GET and POST requests with a textual representation of the current time.

Example 10-2. The DaytimeServlet supporting basic HTTP access

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class DaytimeServlet extends HttpServlet {

  public Date getDate() {
    return new Date();
  }

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    res.setContentType("text/plain");
    PrintWriter out = res.getWriter();
    out.println(getDate().toString());
  }

  public void doPost(HttpServletRequest req, HttpServletResponse res)
                                throws ServletException, IOException {
    doGet(req, res);
  }
}

This servlet's class files should be placed in the standard location for servlets, typically server_root/servlets. Once you place them there, they can be accessed by any web browser using the URL http://server:port/servlet/DaytimeServlet.

10.2.2.2. Back to the applet

Now, for our DaytimeApplet to access this servlet, it must behave just like a browser and make an HTTP connection to the servlet URL, as the implementation of getDateUsingHttpText() in Example 10-3 shows.

Example 10-3. DaytimeApplet getting the time using HTTP

import java.net.URL;                     // New addition
import com.oreilly.servlet.HttpMessage;  // A support class, shown later

  private String getDateUsingHttpText() {
    try {
      // Construct a URL referring to the servlet
      URL url = new URL(getCodeBase(), "/servlet/DaytimeServlet");

      // Create a com.oreilly.servlet.HttpMessage to communicate with that URL
      HttpMessage msg = new HttpMessage(url);

      // Send a GET message to the servlet, with no query string
      // Get the response as an InputStream
      InputStream in = msg.sendGetMessage();

      // Wrap the InputStream with a DataInputStream
      DataInputStream result =
        new DataInputStream(new BufferedInputStream(in));

      // Read the first line of the response, which should be
      // a string representation of the current time
      String date = result.readLine();

      // Close the InputStream
      in.close();

      // Return the retrieved time
      return date;
    }
    catch (Exception e) {
      // If there was a problem, print to System.out
      // (typically the Java console) and return null
      e.printStackTrace();
      return null;
    }
  }

This method retrieves the current time on the server using a text-based HTTP connection. First, it creates a URL object that refers to the DaytimeServlet running on the server. The server host and port for this URL come from the applet's own getCodeBase() method. This guarantees that it matches the host and port from which the applet was downloaded. Then, the method creates an HttpMessage object to communicate with that URL. This object does all the dirty work involved in making the connection. The applet asks it to make a GET request of the DaytimeServlet and then reads the response from the returned InputStream.

The code for HttpMessage is shown in Example 10-4. It is loosely modeled after the ServletMessage class written by Rod McChesney of Sun Microsystems.

Example 10-4. The HttpMessage support class

package com.oreilly.servlet;

import java.io.*;
import java.net.*;
import java.util.*;

public class HttpMessage {

  URL servlet = null;
  String args = null;

  public HttpMessage(URL servlet) {
    this.servlet = servlet;
  }

  // Performs a GET request to the previously given servlet
  // with no query string.
  public InputStream sendGetMessage() throws IOException {
    return sendGetMessage(null);
  }

  // Performs a GET request to the previously given servlet.
  // Builds a query string from the supplied Properties list.
  public InputStream sendGetMessage(Properties args) throws IOException {
    String argString = "";  // default

    if (args != null) {
      argString = "?" + toEncodedString(args);
    }
    URL url = new URL(servlet.toExternalForm() + argString); 

    // Turn off caching
    URLConnection con = url.openConnection();
    con.setUseCaches(false);

    return con.getInputStream();
  }

  // Performs a POST request to the previously given servlet
  // with no query string.
  public InputStream sendPostMessage() throws IOException {
    return sendPostMessage(null);
  }

  // Performs a POST request to the previously given servlet.
  // Builds post data from the supplied Properties list.
  public InputStream sendPostMessage(Properties args) throws IOException {
    String argString = "";  // default
    if (args != null) {
      argString = toEncodedString(args);  // notice no "?"
    }

    URLConnection con = servlet.openConnection();

    // Prepare for both input and output
    con.setDoInput(true);
    con.setDoOutput(true);

    // Turn off caching
    con.setUseCaches(false);

    // Work around a Netscape bug
    con.setRequestProperty("Content-Type",
                           "application/x-www-form-urlencoded");

    // Write the arguments as post data
    DataOutputStream out = new DataOutputStream(con.getOutputStream());
    out.writeBytes(argString);
    out.flush();
    out.close();

    return con.getInputStream();
  }

  // Converts a Properties list to a URL-encoded query string
  private String toEncodedString(Properties args) {
    StringBuffer buf = new StringBuffer();
    Enumeration names = args.propertyNames();
    while (names.hasMoreElements()) {
      String name = (String) names.nextElement();
      String value = args.getProperty(name);
      buf.append(URLEncoder.encode(name) + "=" + URLEncoder.encode(value));
      if (names.hasMoreElements()) buf.append("&");
    }
    return buf.toString();
  }
}

Some of you may have been expecting the HttpMessage class to establish a raw socket connection to the server and proceed to speak HTTP. This approach would certainly work, but it isn't necessary. The higher-level java.net.URL and java.net.URLConnection classes already provide this functionality in a convenient abstraction.

Let's do a quick walk-through of HttpMessage. HttpMessage is designed to communicate with just one URL, the URL given in its constructor. It can send multiple GET and/or POST requests to that URL, but it always communicates with just the one URL.

The code HttpMessage uses to send a GET message is fairly simple. First, sendGetMessage() creates a URL-encoded query string from the passed-in java.util.Properties list. Then, it appends this query string to the saved URL, creating a new URL object. At this point, it could elect to use this new URL (named url) to communicate with the servlet. A call to url.openStream() would return an InputStream that contains the response. But, unfortunately for our purposes, by default all connections made using a URL object are cached. We don't want this--we want the current time, not the time of the last request. So HttpMessage has to turn caching off.[3] The URL class doesn't directly support this low-level control, so HttpMessage gets the URL object's URLConnection and instructs it not to use caching. Finally, HttpMessage returns the URLConnection object's InputStream, which contains the servlet's response.

[3] Actually, we could leave it up to the servlet to turn caching off, by having it set its Pragma header to "no-cache". But it can't hurt to have it in the applet as well.

The code HttpMessage uses to send a POST request (sendPostMessage()) is similar. The major difference is that it directly writes the URL-encoded parameter information in the body of the request. This follows the protocol for how POST requests submit their information. The other difference is that HttpMessage manually sets the request's content type to "application/x-www-form-urlencoded". This should be set automatically by Java, but setting it manually works around a bug in some versions of Netscape's browser.

We should mention that HttpMessage is a general-purpose class for HTTP communication. It doesn't have to be used by applets, and it doesn't have to connect to servlets. It's usable by any Java client that needs to connect to an HTTP resource. It's included in the com.oreilly.servlet package, though, because it's often useful for applet-servlet communication.

For the HttpMessage class to be usable by applets, it has to be made available for downloading along with the applet classes. This means it must be placed in the proper location under the web server's document root. For the Java Web Server, this location is server_root/public_html/com/oreilly/servlet. We recommend you copy the class there from wherever you originally installed the com.oreilly.servlet package (probably server_root/classes/com/oreilly/servlet).

Note that HttpMessage as currently written does not provide a mechanism for an applet to either set or get the HTTP headers associated with its request and response. The URLConnection class, however, supports HTTP header access with its setRequestProperty() and getHeaderField() methods. You can add this functionality if you need it.

Now, with all this code working together, we have an applet that retrieves the current time from its server using text-based HTTP applet-servlet communication. If you try it yourself, you should see the "HTTP text" date filled in, while the rest of the dates are still marked "unavailable."

10.2.3. Object-based HTTP Communication

With a few modifications, we can have the DaytimeApplet receive the current time as a serialized Date object.

10.2.3.1. The servlet

For backward compatibility, let's change our DaytimeServlet to return a serialized Date only if the request asks for it by passing a "format" parameter with the value "object". The code is given in Example 10-5.

Example 10-5. The DaytimeServlet using HTTP to serve an object

import java.io.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public class DaytimeServlet extends HttpServlet {

  public Date getDate() {
    return new Date();
  }

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    // If the client says "format=object" then
    // return the Date as a serialized object
    if ("object".equals(req.getParameter("format"))) {
      ObjectOutputStream out = new ObjectOutputStream(res.getOutputStream());
      out.writeObject(getDate());
    }
    // Otherwise send the Date as a normal string
    else {
      PrintWriter out = res.getWriter();
      out.println(getDate().toString());
    }
  }

  public void doPost(HttpServletRequest req, HttpServletResponse res)
                                throws ServletException, IOException {
    doGet(req, res);
  }
}

As the code shows, sending a serialized Java object is quite simple. This technique can be used to send any primitive types and/or any Java objects that implement the Serializable interface, including a Vector that contains Serializable objects. Multiple objects can also be written to the same ObjectOutputStream, as long as the class receiving the objects reads them in the same order and casts them to the same types.

You may notice that the servlet didn't set the content type of the response to indicate it contained a serialized Java object. The reason is that currently there are no standard MIME types to represent serialized objects. This doesn't really matter, though. A content type acts solely as an indication to the client of how to handle or display the response. If an applet already assumes it's receiving a specific serialized Java object, everything works fine. Sometimes, though, it's useful to use a custom MIME type (specific to your application), so that a servlet can indicate to an applet the contents of its response.

10.2.3.2. The applet

The applet code to retrieve the serialized Date object is very similar to the code to retrieve plain text. The getDateUsingHttpObject() method is shown in Example 10-6.

Example 10-6. The DaytimeApplet using HTTP to retrieve an object

private String getDateUsingHttpObject() {
  try {
    // Construct a URL referring to the servlet
    URL url = new URL(getCodeBase(), "/servlet/DaytimeServlet");

    // Create a com.oreilly.servlet.HttpMessage to communicate with that URL
    HttpMessage msg = new HttpMessage(url);

    // Construct a Properties list to say format=object
    Properties props = new Properties();
    props.put("format", "object");

    // Send a GET message to the servlet, passing "props" as a query string
    // Get the response as an ObjectInputStream
    InputStream in = msg.sendGetMessage(props);
    ObjectInputStream result = new ObjectInputStream(in);

    // Read the Date object from the stream
    Object obj = result.readObject();
    Date date = (Date)obj;

    // Return the string representation of the Date
    return date.toString();
  }
  catch (Exception e) {
    // If there was a problem, print to System.out
    // (typically the Java console) and return null
    e.printStackTrace();
    return null;
  }
}

There are two differences between this method and the getDateUsingHttpText() method. First, this method creates a Properties list to set the "format" parameter to the value "object". This tells DaytimeServlet to return a serialized object. Second, the new method reads the returned content as an Object, using an ObjectInputStream and its readObject() method.

If the class being serialized is not part of the Java Core API (and therefore isn't already available to the applet), it too has to be made available in the proper location under the web server's document root. An applet can always receive an object's serialized contents, but it needs to download its class file to fully reconstruct the object.

Now the applet can retrieve the current time using both text-based and object-based HTTP communication. If you try it yourself now (with a web browser or applet viewer that supports JDK 1.1), you should see both the "HTTP text" and "HTTP object" fields filled in.

10.2.3.3. Posting a serialized object

Before we go on, we should look at one more (hitherto unmentioned) method from the HttpMessage class: sendPostMessage(Serializable) . This method helps an applet upload a serialized object to a servlet using the POST method. This object transfer isn't particularly useful to our daytime server example (and is kind of out of place here), but we mention it because it can come in handy when an applet needs to upload complicated data structures to its server. Example 10-7 contains the code for this method.

Example 10-7. Posting a serialized object

// Uploads a serialized object with a POST request.
// Sets the content type to java-internal/classname.
public InputStream sendPostMessage(Serializable obj) throws IOException {
  URLConnection con = servlet.openConnection();

  // Prepare for both input and output
  con.setDoInput(true);
  con.setDoOutput(true);

  // Turn off caching
  con.setUseCaches(false);

  // Set the content type to be java-internal/classname
  con.setRequestProperty("Content-Type",
                         "java-internal/" + obj.getClass().getName());

  // Write the serialized object as post data
  ObjectOutputStream out = new ObjectOutputStream(con.getOutputStream());
  out.writeObject(obj);
  out.flush();
  out.close();

  return con.getInputStream();
}

An applet uses sendPostMessage(Serializable) just as it uses sendPostMessage(Properties). Here is the code for an applet that uploads any exceptions it encounters to a servlet:

catch (Exception e) {
  URL url = new URL(getCodeBase(), "/servlet/ExceptionLogger");
  HttpMessage msg = new HttpMessage(url);
  InputStream in = msg.sendPostMessage(e);
}

The servlet, meanwhile, receives the Exception in its doPost() method like this:

ObjectInputStream objin = new ObjectInputStream(req.getInputStream());
Object obj = objin.readObject();
Exception e = (Exception) obj;

The servlet can receive the type of the uploaded object as the subtype (second half) of the content type. Note that this sendPostMessage(Serializable) method uploads just one object at a time and uploads only serializable objects (that is, no primitive types).

10.2.4. Socket Communication

Now let's take a look at how an applet and servlet can communicate using non-HTTP socket communication.

10.2.4.1. The servlet

The servlet's role in this communication technique is that of a passive listener. Due to security restrictions, only the applet can initiate a socket connection. A servlet must be content to listen on a socket port and wait for an applet to connect. Generally speaking, a servlet should begin listening for applet connections in its init() method and stop listening in its destroy() method. In between, for every connection it receives, it should spawn a handler thread to communicate with the client.

With HTTP socket connections, these nitty-gritty details are managed by the web server. The server listens for incoming HTTP requests and dispatches them as appropriate, calling a servlet's service(), doGet(), or doPost() methods as necessary. But when a servlet opts not to use HTTP communication, the web server can't provide any help. The servlet acts, in essence, like its own server and thus has to manage the socket connections itself.

Okay, maybe we scared you a bit more than we had to there. The truth is that we can write a servlet superclass that abstracts away the details involved in managing socket connections. This class, which we call DaemonHttpServlet, can be extended by any servlet wanting to make itself available via non-HTTP socket communication.[4]

[4] The name "daemon" was chosen to refer to Unix daemons, programs that run in the background quietly handling certain events. And where did those programs get the "daemon" moniker? According to the New Hacker's Dictionary, it originally came "from the mythological meaning, (but was) later rationalized as the acronym `Disk And Execution MONitor'".

DaemonHttpServlet starts listening for client requests in its init() method and stops listening in its destroy() method. In between, for every connection it receives, it calls the abstract handleClient(Socket) method. This method should be implemented by any servlet that subclasses DaemonHttpServlet.

Example 10-8 shows how DaytimeServlet extends DaemonHttpServlet and implements handleClient() to make itself available via non-HTTP socket communication.

Example 10-8. The DaytimeServlet acting as a non-HTTP server

import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

import com.oreilly.servlet.DaemonHttpServlet;

public class DaytimeServlet extends DaemonHttpServlet {

  public Date getDate() {
    return new Date();
  }

  public void init(ServletConfig config) throws ServletException {
    // As before, if you override init() you have to call super.init()
    super.init(config);
  }

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                               throws ServletException, IOException {
    // If the client says "format=object" then
    // send the Date as a serialized object
    if ("object".equals(req.getParameter("format"))) {
      ObjectOutputStream out = new ObjectOutputStream(res.getOutputStream());
      out.writeObject(getDate());
    }
    // Otherwise send the Date as a normal ASCII string
    else {
      PrintWriter out = res.getWriter();
      out.println(getDate().toString());
    }
  }

  public void doPost(HttpServletRequest req, HttpServletResponse res)
                                throws ServletException, IOException {
    doGet(req, res);
  }

  public void destroy() {
    // Now, unlike before, if you override destroy() you also have to call
    // super.destroy()
    super.destroy();
  }

  // Handle a client's socket connection by spawning a DaytimeConnection
  // thread.
  public void handleClient(Socket client) {
    new DaytimeConnection(this, client).start();
  }
}

class DaytimeConnection extends Thread {

  DaytimeServlet servlet;
  Socket client;

  DaytimeConnection(DaytimeServlet servlet, Socket client) {
    this.servlet = servlet;
    this.client = client;
    setPriority(NORM_PRIORITY - 1);
  }

  public void run() {
    try {
      // Read the first line sent by the client
      DataInputStream in = new DataInputStream(
                           new BufferedInputStream(
                           client.getInputStream()));
      String line = in.readLine();

      // If it was "object" then return the Date as a serialized object
      if ("object".equals(line)) {
        ObjectOutputStream out =
          new ObjectOutputStream(client.getOutputStream());
        out.writeObject(servlet.getDate());
        out.close();
      }
      // Otherwise, send the Date as a normal string
      else {
        // Wrap a PrintStream around the Socket's OutputStream
        PrintStream out = new PrintStream(client.getOutputStream());
        out.println(servlet.getDate().toString());
        out.close();
      }

      // Be sure to close the connection
      client.close();
    }
    catch (IOException e) {
      servlet.getServletContext()
        .log(e, "IOException while handling client request");
    }
    catch (Exception e) {
      servlet.getServletContext()
        .log("Exception while handling client request");
    }
  }
}

The DaytimeServlet class remains largely unchanged from its previous form. The major difference is that it extends DaemonHttpServlet and implements a handleClient(Socket) method that spawns a new DaytimeConnection thread. This DaytimeConnection instance bears the responsibility for handling a specific socket connection.

DaytimeConnection works as follows. When it is created, it saves a reference to the DaytimeServlet, so that it can call the servlet's getDate() method, and a reference to the Socket, so that it can communicate with the client. DaytimeConnection also sets its running priority to one less than normal, to indicate that this communication can wait if necessary while other threads perform more time-critical work.

Immediately after it creates the DaytimeConnection thread, DaytimeServlet starts the thread, causing its run() method to be called. In this method, the DaytimeConnection communicates with the client using some unnamed (but definitely not HTTP) protocol. It begins by reading the first line sent by the client. If the line is "object", it returns the current time as a serialized Date object. If the line is anything else, it returns the current time as a normal string. When it is done, it closes the connection.

10.2.4.2. The superclass

The low-level socket management is done in the DaemonHttpServlet class. Generally, this class can be used without modification, but it is useful to understand the internals. The code is shown in Example 10-9.

Example 10-9. The DaemonHttpServlet superclass

package com.oreilly.servlet;

import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public abstract class DaemonHttpServlet extends HttpServlet {

  protected int DEFAULT_PORT = 1313;  // not static or final
  private Thread daemonThread;

  public void init(ServletConfig config) throws ServletException {
    super.init(config);

    // Start a daemon thread
    try {
      daemonThread = new Daemon(this);
      daemonThread.start();
    }
    catch (Exception e) {
      getServletContext().log(e, "Problem starting socket server daemon thread");
    }
  }

  // Returns the socket port on which this servlet will listen.
  // A servlet can specify the port in three ways: by using the socketPort
  // init parameter, by setting the DEFAULT_PORT variable before calling
  // super.init(), or by overriding this method's implementation
  protected int getSocketPort() {
    try { return Integer.parseInt(getInitParameter("socketPort")); }
    catch (NumberFormatException e) { return DEFAULT_PORT; }
  }

  abstract public void handleClient(Socket client);

  public void destroy() {
    // Stop the daemon thread
    try {
      daemonThread.stop();
      daemonThread = null;
    }
    catch (Exception e) {
      getServletContext().log(e, "Problem stopping server socket daemon thread");
    }
  }
}

// This work is broken into a helper class so that subclasses of
// DaemonHttpServlet can define their own run() method without problems.

class Daemon extends Thread {

  private ServerSocket serverSocket;
  private DaemonHttpServlet servlet;

  public Daemon(DaemonHttpServlet servlet) {
    this.servlet = servlet;
  }

  public void run() {
    try {
      // Create a server socket to accept connections
      serverSocket = new ServerSocket(servlet.getSocketPort());
    }
    catch (Exception e) {
      servlet.getServletContext().log(e, "Problem establishing server socket");
      return;
    }

    try {
      while (true) {
        // As each connection comes in, call the servlet's handleClient().
        // Note this method is blocking. It's the servlet's responsibility
        // to spawn a handler thread for long-running connections.
        try {
          servlet.handleClient(serverSocket.accept());
        }
        catch (IOException ioe) {
          servlet.getServletContext()
            .log(ioe, "Problem accepting client's socket connection");
        }
      }
    }
    catch (ThreadDeath e) {
      // When the thread is killed, close the server socket
      try {
        serverSocket.close();
      }
      catch (IOException ioe) {
        servlet.getServletContext().log(ioe, "Problem closing server socket");
      }
    }
  }
}

The init() method of DaemonHttpServlet creates and starts a new Daemon thread that is in charge of listening for incoming connections. The destroy() method stops the thread. This makes it imperative that any servlet subclassing DaemonHttpServlet call super.init(config) and super.destroy() if the servlet implements its own init() and destroy() methods.

The Daemon thread begins by establishing a ServerSocket to listen on some specific socket port. Which socket port is determined with a call to the servlet's getSocketPort() method. The value returned is either the value of the init parameter "socketPort", or, if that init parameter doesn't exist, the current value of the variable DEFAULT_PORT. A servlet may choose to override the getSocketPort() implementation if it so desires.

After establishing the ServerSocket, the Daemon thread waits for incoming requests with a call to serverSocket.accept() . This method is blocking--it stops this thread's execution until a client attaches to the server socket. When this happens, the accept() method returns a Socket object that the Daemon thread passes immediately to the servlet's handleClient() method. This handleClient() method usually spawns a handler thread and returns immediately, leaving the Daemon thread ready to accept another connection.

The server socket clean-up is equally as important as its set-up. We have to be sure the server socket lives as long as the servlet, but no longer. To this end, the destroy() method of DaemonHttpServlet calls the Daemon thread's stop() method. This call doesn't immediately stop the Daemon thread, however. It just causes a ThreadDeath exception to be thrown in the Daemon thread at its current point of execution. The Daemon thread catches this exception and closes the server socket.

There are two caveats in writing a servlet that acts like a non-HTTP server. First, only one servlet at a time can listen to any particular socket port. This makes it vital that each daemon servlet choose its own socket port--by setting its socketPort init parameter, setting the DEFAULT_PORT variable before calling super.init(config), or overriding getSocketPort() directly. Second, a daemon servlet must be loaded into its server and have its init() method called before it can accept incoming non-HTTP connections. Thus, you should either tell your server to load it at start-up or be sure it is always accessed via HTTP before it is accessed directly.

10.2.4.3. The applet

The applet code to connect to the servlet using non-HTTP communication, primarily the getDateUsingSocketText() and getDateUsingSocketObject() methods, is shown in Example 10-10.

Example 10-10. The DaytimeApplet getting the time using a socket connection

import java.net.Socket;                    // New addition

static final int DEFAULT_PORT = 1313;   // New addition

private int getSocketPort() {
  try { return Integer.parseInt(getParameter("socketPort")); }
  catch (NumberFormatException e) { return DEFAULT_PORT; }
}

private String getDateUsingSocketText() {
  InputStream in = null;
  try {
    // Establish a socket connection with the servlet
    Socket socket = new Socket(getCodeBase().getHost(), getSocketPort());

    // Print an empty line, indicating we want the time as plain text
    PrintStream out = new PrintStream(socket.getOutputStream());
    out.println();
    out.flush();

    // Read the first line of the response
    // It should contain the current time
    in = socket.getInputStream();
    DataInputStream result =
      new DataInputStream(new BufferedInputStream(in));
    String date = result.readLine();

    // Return the retrieved string
    return date;
  }
  catch (Exception e) {
    // If there was a problem, print to System.out
    // (typically the Java console) and return null
    e.printStackTrace();
    return null;
  }
  finally {
    // Always close the connection
    // This code executes no matter how the try block completes
    if (in != null) {
      try { in.close(); }
      catch (IOException ignored) { }
    }
  }
}

private String getDateUsingSocketObject() {
  InputStream in = null;
  try {
    // Establish a socket connection with the servlet
    Socket socket = new Socket(getCodeBase().getHost(), getSocketPort());

    // Print a line saying "object", indicating we want the time as 
    // a serialized Date object
    PrintStream out = new PrintStream(socket.getOutputStream());
    out.println("object");
    out.flush();

    // Create an ObjectInputStream to read the response
    in = socket.getInputStream();
    ObjectInputStream result =
      new ObjectInputStream(new BufferedInputStream(in));

    // Read an object, and cast it to be a Date
    Object obj = result.readObject();
    Date date = (Date)obj;

    // Return a string representation of the retrieved Date
    return date.toString();
  }
  catch (Exception e) {
    // If there was a problem, print to System.out
    // (typically the Java console) and return null
    e.printStackTrace();
    return null;
  }
  finally {
    // Always close the connection
    // This code executes no matter how the try block completes
    if (in != null) {
      try { in.close(); }
      catch (IOException ignored) { }
    }
  }
}

For both these methods, the applet begins by creating a Socket that is used to communicate with the servlet. To do this, it needs to know both the host name and the port number on which the servlet is listening. Determining the host is easy--it has to be the same host from which it was downloaded, accessible with a call to getCodeBase().getHost(). The port is harder, as it depends entirely on the servlet to which this applet is connecting. This applet uses the getSocketPort() method to make this determination. The implementation of getSocketPort() shown here returns the value of the applet's socketPort parameter, or (if that parameter isn't given) returns the value of the DEFAULT_PORT variable.

Once it has established a socket connection, the applet follows an unnamed protocol to communicate with the servlet. This protocol requires that the applet send one line to indicate whether it wants the current time as text or as an object. If the line says "object", it receives an object. If it says anything else, it receives plain text. After sending this line, the applet can read the response as appropriate.

The applet and servlet could continue to communicate using this socket. That's one of the major advantages of not using HTTP communication. But, in this case, the applet got what it wanted and just needs to close the connection. It performs this close in a finally block. Putting the close here guarantees that the connection is closed whether the try throws an exception or not.

With the addition of these two methods our applet is nearly complete. If you run it now, you should see that all of the fields except "RMI object" contain dates.

10.2.5. RMI Communication

Earlier in this chapter, we pointed out that one of the reasons not to use RMI communication is that it's complicated. Although that's true, it's also true that with the help of another servlet superclass, the code required for a servlet to make itself available via RMI communication can be ridiculously simple. First, we'll lead you through the step-by-step instructions on how to make a servlet a remote object. Then, after you've seen how simple and easy that is, we'll explain all the work going on behind the scenes.

10.2.5.1. The servlet

To begin with, all RMI remote objects must implement a specific interface. This interface does two things: it declares which methods of the remote object are to be made available to remote clients, and it extends the Remote interface to indicate it's an interface for a remote object. For our DaytimeServlet, we can write the DaytimeServer interface shown in Example 10-11.

Example 10-11. The DaytimeServer interface

import java.util.Date;
import java.rmi.Remote;
import java.rmi.RemoteException;

public interface DaytimeServer extends Remote {
  public Date getDate() throws RemoteException;
}

This interface declares that our DaytimeServlet makes its getDate() method available to remote clients. Notice that the getDate() signature has been altered slightly--it now throws a RemoteException. Every method made available via RMI must declare that it throws this exception. Although the method itself may not throw the exception, it can be thrown by the system to indicate a network service failure.

The code for DaytimeServlet remains mostly unchanged from its original version. In fact, the only changes are that it now implements DaytimeServer and extends com.oreilly.servlet.RemoteHttpServlet, the superclass that allows this servlet to remain so unchanged. The servlet also implements a destroy() method that calls super.destroy(). It's true that this method is perfectly useless in this example, but it points out that any destroy() method implemented in a remote servlet must call super.destroy() to give the RemoteHttpServlet object's destroy() method a chance to terminate RMI communication. Example 10-12 shows the new DaytimeServlet code.

Example 10-12. The DaytimeServlet now supporting RMI access

import java.io.*;
import java.net.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

import com.oreilly.servlet.RemoteHttpServlet;          // New addition

public class DaytimeServlet extends RemoteHttpServlet  // New addition
                            implements DaytimeServer { // New addition

  // The single method from DaytimeServer
  // Note: the throws clause isn't necessary here
  public Date getDate() {
    return new Date();
  }

  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    // Additional code could go here
  }

  public void doGet(HttpServletRequest req, HttpServletResponse res)
                            throws ServletException, IOException {
    // If the client says "format=object" then
    // send the Date as a serialized object
    if ("object".equals(req.getParameter("format"))) {
      ObjectOutputStream out = new ObjectOutputStream(res.getOutputStream());
      out.writeObject(getDate());
    }
    // Otherwise send the Date as a normal ASCII string
    else {
      PrintWriter out = res.getWriter();
      out.println(getDate().toString());
    }
  }

  public void doPost(HttpServletRequest req, HttpServletResponse res)
                                throws ServletException, IOException {
    doGet(req, res);
  }

  public void destroy() {
    // If you override destroy() you have to call super.destroy()
    super.destroy();
  }
}

So that's how to write a remote object servlet. We suggest you place such servlets directly in the server's classpath (server_root/classes) so they aren't reloaded, since reloading a remote object tends to cause unexpected results. Compiling a remote object servlet is the same as for every other servlet, with one additional step. After compiling the servlet source code, you now have to compile the servlet class with the RMI compiler rmic. The RMI compiler takes a remote object's class file and generates stuband skeleton versions of the class. These classes work behind the scenes to enable RMI communication. You don't need to worry about the details, but you should know that the stub helps the client invoke methods on the remote object and the skeleton helps the server handle those invocations.

Using rmic is similar to using javac. For this example you can compile DaytimeServlet with the following command:

% rmic DaytimeServlet

Notice that you provide rmic with a Java class name to compile, not a file. Thus, if the servlet to compile is part of a package it should be given to rmic as package.name.ServletName. The rmic program can take a classpath to search with the -classpath parameter, as well as a destination directory for the stub and skeleton files with the -d parameter.

After executing the above rmic command, you should see two new class files: DaytimeServlet_Stub.class and DaytimeServlet_Skel.class. We'll tell you what to do with these in just a minute. First, you should know that you don't have to rerun the RMI compiler every time you modify the remote servlet's code. This is because the stub and skeleton classes are built in terms of the servlet's interface, not its implementation of that interface. Accordingly, you need to regenerate them only when you modify the DaytimeServer interface (or your equivalent interface).

Now, for the final step in writing a remote servlet: copying a few class files to the server's document root, where they can be downloaded by an applet. There are two class files that need to be downloaded: the stub class DaytimeServlet_Stub.class and the remote interface class DaytimeServer.class. The client (in this case the applet) needs the stub class to perform its half of the RMI communication, and the stub class itself uses the remote interface class. Be aware that the servlet needs to use these classes, too, so copy them to the server's document root and leave them in the server's classpath.[5]Figure 10-2 shows where all the server files go.

[5] Managing multiple class files can become a serious headache during development. On a Unix system, you can use soft links to simplify the task. Or, on any system, you can implement a more general-purpose solution: change the server's classpath to include server_root/public_html/classes. Put the interface class and stub class in there. Then the server can find them in its new classpath and the applet's codebase can be set to /classes to find them as well.

 

 

 

 

 

 

 

 

Figure 10-2. File locations for RMI communication

That's it! If you follow these instructions you should be able to get a remote servlet operating in short order. Now let's look at the RemoteHttpServlet class and see what's going on behind the scenes.

10.2.5.2. The superclass

A remote object needs to do two things to prepare itself for RMI communication: it needs to export itself and register itself. When a remote object exports itself, it begins listening on a port for incoming method invocation requests. When a remote object registers itself, it tells a registry server its name and port number, so that clients can locate it (essentially, find out its port number) and communicate with it. These two tasks are handled by the RemoteHttpServlet class, shown in Example 10-13.

Example 10-13. The RemoteHttpServlet superclass

package com.oreilly.servlet;

import java.io.*;
import java.net.*;
import java.rmi.*;
import java.rmi.server.*;
import java.rmi.registry.*;
import java.util.*;
import javax.servlet.*;
import javax.servlet.http.*;

public abstract class RemoteHttpServlet extends HttpServlet
                                        implements Remote {

  protected Registry registry;

  public void init(ServletConfig config) throws ServletException {
    super.init(config);
    try {
      // Export ourself
      UnicastRemoteObject.exportObject(this);
      // Register ourself
      bind();
    }
    catch (RemoteException e) {
      getServletContext().log(e, "Problem binding to RMI registry");
    }
  }

  public void destroy() {
    // Unregister ourself
    unbind();
  }

  // Returns the name under which we are to be registered
  protected String getRegistryName() {
    // First name choice is the "registryName" init parameter
    String name = getInitParameter("registryName");
    if (name != null) return name;

    // Fallback choice is the name of this class
    return this.getClass().getName();
  }

  // Returns the port on which the registry server is listening
  // (or should be listening)
  protected int getRegistryPort() {
    // First port choice is the "registryPort" init parameter
    try { return Integer.parseInt(getInitParameter("registryPort")); }

    // Fallback choice is the default registry port (1099)
    catch (NumberFormatException e) { return Registry.REGISTRY_PORT; }
  }

  protected void bind() {
    // Try to find the appropriate registry already running
    try {
      registry = LocateRegistry.getRegistry(getRegistryPort());
      registry.list();  // Verify it's alive and well
    }
    catch (Exception e) {
      // Couldn't get a valid registry
      registry = null;
    }

    // If we couldn't find it, we need to create it.
    // (Equivalent to running "rmiregistry")
    if (registry == null) {
      try {
        registry = LocateRegistry.createRegistry(getRegistryPort());
      }
      catch (Exception e) { 
        log("Could not get or create RMI registry on port " +
            getRegistryPort() + ": " + e.getMessage());
        return;
      }
    }

    // If we get here, we must have a valid registry.
    // Now register this servlet instance with that registry.
    // "Rebind" to replace any other objects using our name.
    try {
      registry.rebind(getRegistryName(), this);
    }
    catch (Exception e) {
      log("Could not bind to RMI registry: " + e.getMessage());
      return;
    }
  }

  protected void unbind() {
    try {
      if (registry != null) registry.unbind(getRegistryName());
    }
    catch (Exception e) {
      getServletContext().log(e, "Problem unbinding from RMI registry");
    }
  }
}

If you've ever used or read about RMI before, you've probably seen remote objects that extend the java.rmi.server.UnicastRemoteObject class. This is the standard--and, in fact, recommended--way to write a remote object. The RemoteHttpServlet class, however, doesn't extend UnicastRemoteObject; it extends HttpServlet. As you may know, Java doesn't support multiple inheritance. This means that RemoteHttpServlet has to choose to extend eitherUnicastRemoteObject or HttpServleteven though it needs functionality from both classes. It's a difficult choice. Whichever class RemoteHttpServlet doesn't extend it has to basically reimplement on its own. In the end, we've extended HttpServlet because it's easier to rewrite the functionality of UnicastRemoteObject than that of HttpServlet.

This rewrite requires RemoteHttpServlet to do two things it wouldn't have to do if it extended UnicastRemoteObject. First, it must declare that it implements the Remote interface. All remote objects must implement this interface, but normally, by extending UnicastRemoteObject, a class gets this for free. Still, the price for going it alone isn't too bad, as the Remote interface doesn't actually define any methods. An object declares that it implements Remote solely to express its desire to be treated as a remote object.

The second thing RemoteHttpServlet has to do is manually export itself. Normally, this is performed automatically in the UnicastRemoteObject() constructor. But again, doing this without that constructor is not a problem. The UnicastRemoteObject class has a static exportObject(Remote) method that any Remote object can use to export itself. RemoteHttpServlet uses this method and exports itself with this single line:

UnicastRemoteObject.exportObject(this);

Those two steps, implementing Remote and exporting itself, are done by RemoteHttpServlet in lieu of extending UnicastRemoteObject.[6]

[6] To be absolutely correct, there is more we need to do. According to the java.rmi.remote.UnicastRemoteObject documentation, "If UnicastRemoteObject is not extended, the implementation class must then assume the responsibility for the correct semantics of the hashCode, equals, and toString methods inherited from the Object class, so that they behave appropriately for remote objects." According to the java.rmi.remote.RemoteRef documentation, "These methods should guarantee that two remote object stubs that refer to the same remote object will have the same hash code (in order to support remote objects as keys in hash tables)." Implementing the mechanism to support this guarantee is fairly difficult and, we believe, not commonly necessary for applet-servlet communication; thus we've t

分享到:
评论

相关推荐

    Applet与Servlet之间的通信与交互

    通过阅读《Applet与Servlet之间的通信与交互》系列文档,我们可以深入理解这两种技术的协作机制,从而更好地设计和实现动态Web应用。文档中的实例代码和解释可以帮助开发者掌握具体的实现细节,提升开发效率。同时,...

    Applet and Servlet Communication

    2. **Applet与Servlet通信的必要性** Applet虽然可以直接与浏览器交互,但其与服务器的通信受到同源策略的限制,因此需要通过Servlet作为中介,来实现与服务器的通信,传递数据或执行服务器端操作。 3. **通信方式...

    applet和servlet的通讯,并打印

    在Applet和Servlet之间进行通信,通常是为了实现一些复杂的交互功能,比如在这个例子中,Applet需要请求Servlet来获取文件流,然后利用该文件流进行打印。 1. **Applet请求Servlet**: 当Applet需要从服务器获取...

    servlet applet通讯的例子

    另一种方式是Applet直接通过URLConnection或HttpURLConnection与Servlet通信,发送请求并接收响应。 2. **URLConnection类**: - TestURLConnection这个文件可能是一个演示如何使用URLConnection来建立Servlet和...

    A-S.rar_servlet applet

    总的来说,"A-S.rar_servlet applet"示例是一个关于Servlet和Applet间通信的经典案例,它利用Java的序列化特性实现了对象在网络上的安全传递。对于学习Java Web开发的人来说,掌握这种通信方式有助于理解Web应用的...

    深入Java Servlet网络编程

    11. 1 Applet和Servlet通信概述 11. 2 Applet和Servlet的网络通信 11. 2. 1 初识Applet和Servlet网络通信 11. 2. 2 Applet和Servlet之间传递对象 11. 3 Applet和Servlet之间实现远程方法调用 11. 3. 1 远程方法调用...

    applet图片扫描程序

    - 交互与通信:如果需要将扫描的图片上传到服务器,Applet可以通过`doGet()`或`doPost()`方法与Servlet进行通信,将图片数据作为HTTP请求的一部分发送。 5. **安全性和限制**:由于Applet运行在客户端,因此存在...

    servlet 8个最基础demo

    我们老师教学演示的servlet例子 从简单到难文件夹有八个demo供初学者学习 servlet01 servlet02 servlet03 servlet04 servlet05 servlet06 servlet07 servlet08 servlet是在服务器上运行的小程序。这个词是在Java ...

    Servlet教程.pdf

    然后,Servlet容器创建Servlet实例并调用其API方法处理请求,构建响应;最后,Web服务器将响应返回给客户端。 Java Servlet API包含在`javax.servlet`和`javax.servlet.http`这两个包中。`javax.servlet`包提供...

    servlet总结

    - **易于编写,功能强大**:Servlet易于开发,能够与多种系统资源交互,包括文件、数据库、Applet、Java应用程序等。 - **与系统资源交互**:Servlet能够访问服务器上的文件系统、调用数据库API,以及与其他Java应用...

    servlet temple

     重新装入 Servlet 时装入 Servlet 后,服务器创建一个 Servlet 实例并且调用 Servlet 的 init() 方法。在初始化阶段,Servlet 初始化参数被传递给 Servlet 配置对象。  (2) 请求处理  对于到达服务器的客户机...

    JSP实例开发源码——聊天系统(java+applet).zip

    【标题】"JSP实例开发源码——聊天系统(java+applet)" 提供了一个基于Java Servlet和Applet技术实现的在线聊天系统的实例。这个项目旨在帮助开发者了解如何使用JSP和Applet技术来构建实时交互的Web应用。 【JSP...

    Servlet课件.doc

    Servlet与Applet不同,Applet运行在客户端的浏览器中,而Servlet则在服务器端运行,处理来自Web客户端的请求。 Servlet是实现CGI(Common Gateway Interface)功能的一种高效方式。CGI允许Web服务器与外部应用程序...

    servlet_串讲

    Servlet与客户端小程序Applet相对,后者运行在用户的浏览器中,而Servlet则在Web服务器内部运行,与客户端进行通信。 Servlet的工作原理基于HTTP协议,当用户通过浏览器发起请求到特定的URL(例如`...

    jsp和servlet帮助文档

    3. **Servlet通信**:Servlet通过`HttpServletRequest`和`HttpServletResponse`对象与客户端进行交互,获取请求参数,设置响应内容。 4. **过滤器和监听器**:Servlet API还提供了过滤器(Filter)和监听器...

    web开发一些技术的业务流程描述(java,http,applet,jsp,servlet,cookie,session)

    - 加载Servlet类并创建其实例。 - 调用init()方法进行初始化。 2. **服务**: - 服务器接收到客户端请求后,调用servlet对象的service()方法。 - service()方法根据请求类型(GET或POST)调用doGet()或doPost()...

    JSP实例开发源码——家庭理财系统(java+applet).zip

    在家庭理财系统中,Applet可能负责数据可视化或与服务器进行通信。 3. **数据库交互** - **JDBC(Java Database Connectivity)**:JSP应用通常需要与数据库进行交互,这里很可能使用了JDBC API来连接和操作数据库...

    吕老师servlet课件

    2. **Servlet与Applet的比较** - Applet是在客户端浏览器中运行的Java小应用程序,有图形界面,而Servlet则在服务器端运行,负责处理业务逻辑和动态响应请求。 - 两者都不是独立应用,没有main()方法,由浏览器或...

    servlet简介

    - **维持服务器与Applet的连接**:Servlet可以开启并保持服务器到Applet的新连接,以便高效地传输数据。 - **MIME类型处理**:Servlet可以对特殊类型的处理,如图像转换或服务器端包括(SSI)。 - **自定义服务器...

    servlet技术简介.pdf

    3. 与其他服务器资源(包括数据库和基于 Java 的应用程序)进行通信。 4. 用多个客户机处理连接,接收多个客户机的输入,并将结果广播到多个客户机上。 5. 当允许在单连接方式下传送数据的情况下,在浏览器上打开...

Global site tag (gtag.js) - Google Analytics