aboutsummaryrefslogtreecommitdiff
path: root/src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java')
-rw-r--r--src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java787
1 files changed, 787 insertions, 0 deletions
diff --git a/src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java b/src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java
new file mode 100644
index 000000000..835a7c7cd
--- /dev/null
+++ b/src/share/classes/sun/rmi/transport/tcp/TCPEndpoint.java
@@ -0,0 +1,787 @@
+/*
+ * Copyright 1996-2005 Sun Microsystems, Inc. All Rights Reserved.
+ * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
+ *
+ * This code is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License version 2 only, as
+ * published by the Free Software Foundation. Sun designates this
+ * particular file as subject to the "Classpath" exception as provided
+ * by Sun in the LICENSE file that accompanied this code.
+ *
+ * This code is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ * version 2 for more details (a copy is included in the LICENSE file that
+ * accompanied this code).
+ *
+ * You should have received a copy of the GNU General Public License version
+ * 2 along with this work; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
+ * CA 95054 USA or visit www.sun.com if you need additional information or
+ * have any questions.
+ */
+package sun.rmi.transport.tcp;
+
+import java.io.DataInput;
+import java.io.DataOutput;
+import java.io.IOException;
+import java.io.ObjectInput;
+import java.io.ObjectOutput;
+import java.net.InetAddress;
+import java.net.ServerSocket;
+import java.net.Socket;
+import java.rmi.ConnectIOException;
+import java.rmi.RemoteException;
+import java.rmi.server.RMIClientSocketFactory;
+import java.rmi.server.RMIServerSocketFactory;
+import java.rmi.server.RMISocketFactory;
+import java.security.AccessController;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedList;
+import java.util.Map;
+import java.util.Set;
+import sun.rmi.runtime.Log;
+import sun.rmi.runtime.NewThreadAction;
+import sun.rmi.transport.Channel;
+import sun.rmi.transport.Endpoint;
+import sun.rmi.transport.Target;
+import sun.rmi.transport.Transport;
+import sun.security.action.GetBooleanAction;
+import sun.security.action.GetIntegerAction;
+import sun.security.action.GetPropertyAction;
+
+/**
+ * TCPEndpoint represents some communication endpoint for an address
+ * space (VM).
+ *
+ * @author Ann Wollrath
+ */
+public class TCPEndpoint implements Endpoint {
+ /** IP address or host name */
+ private String host;
+ /** port number */
+ private int port;
+ /** custom client socket factory (null if not custom factory) */
+ private final RMIClientSocketFactory csf;
+ /** custom server socket factory (null if not custom factory) */
+ private final RMIServerSocketFactory ssf;
+
+ /** if local, the port number to listen on */
+ private int listenPort = -1;
+ /** if local, the transport object associated with this endpoint */
+ private TCPTransport transport = null;
+
+ /** the local host name */
+ private static String localHost;
+ /** true if real local host name is known yet */
+ private static boolean localHostKnown;
+
+ // this should be a *private* method since it is privileged
+ private static int getInt(String name, int def) {
+ return AccessController.doPrivileged(new GetIntegerAction(name, def));
+ }
+
+ // this should be a *private* method since it is privileged
+ private static boolean getBoolean(String name) {
+ return AccessController.doPrivileged(new GetBooleanAction(name));
+ }
+
+ /**
+ * Returns the value of the java.rmi.server.hostname property.
+ */
+ private static String getHostnameProperty() {
+ return AccessController.doPrivileged(
+ new GetPropertyAction("java.rmi.server.hostname"));
+ }
+
+ /**
+ * Find host name of local machine. Property "java.rmi.server.hostname"
+ * is used if set, so server administrator can compensate for the possible
+ * inablility to get fully qualified host name from VM.
+ */
+ static {
+ localHostKnown = true;
+ localHost = getHostnameProperty();
+
+ // could try querying CGI program here?
+ if (localHost == null) {
+ try {
+ InetAddress localAddr = InetAddress.getLocalHost();
+ byte[] raw = localAddr.getAddress();
+ if ((raw[0] == 127) &&
+ (raw[1] == 0) &&
+ (raw[2] == 0) &&
+ (raw[3] == 1)) {
+ localHostKnown = false;
+ }
+
+ /* if the user wishes to use a fully qualified domain
+ * name then attempt to find one.
+ */
+ if (getBoolean("java.rmi.server.useLocalHostName")) {
+ localHost = FQDN.attemptFQDN(localAddr);
+ } else {
+ /* default to using ip addresses, names will
+ * work across seperate domains.
+ */
+ localHost = localAddr.getHostAddress();
+ }
+ } catch (Exception e) {
+ localHostKnown = false;
+ localHost = null;
+ }
+ }
+
+ if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+ TCPTransport.tcpLog.log(Log.BRIEF,
+ "localHostKnown = " + localHostKnown +
+ ", localHost = " + localHost);
+ }
+ }
+
+ /** maps an endpoint key containing custom socket factories to
+ * their own unique endpoint */
+ // TBD: should this be a weak hash table?
+ private static final
+ Map<TCPEndpoint,LinkedList<TCPEndpoint>> localEndpoints =
+ new HashMap<TCPEndpoint,LinkedList<TCPEndpoint>>();
+
+ /**
+ * Create an endpoint for a specified host and port.
+ * This should not be used by external classes to create endpoints
+ * for servers in this VM; use getLocalEndpoint instead.
+ */
+ public TCPEndpoint(String host, int port) {
+ this(host, port, null, null);
+ }
+
+ /**
+ * Create a custom socket factory endpoint for a specified host and port.
+ * This should not be used by external classes to create endpoints
+ * for servers in this VM; use getLocalEndpoint instead.
+ */
+ public TCPEndpoint(String host, int port, RMIClientSocketFactory csf,
+ RMIServerSocketFactory ssf)
+ {
+ if (host == null)
+ host = "";
+ this.host = host;
+ this.port = port;
+ this.csf = csf;
+ this.ssf = ssf;
+ }
+
+ /**
+ * Get an endpoint for the local address space on specified port.
+ * If port number is 0, it returns shared default endpoint object
+ * whose host name and port may or may not have been determined.
+ */
+ public static TCPEndpoint getLocalEndpoint(int port) {
+ return getLocalEndpoint(port, null, null);
+ }
+
+ public static TCPEndpoint getLocalEndpoint(int port,
+ RMIClientSocketFactory csf,
+ RMIServerSocketFactory ssf)
+ {
+ /*
+ * Find mapping for an endpoint key to the list of local unique
+ * endpoints for this client/server socket factory pair (perhaps
+ * null) for the specific port.
+ */
+ TCPEndpoint ep = null;
+
+ synchronized (localEndpoints) {
+ TCPEndpoint endpointKey = new TCPEndpoint(null, port, csf, ssf);
+ LinkedList<TCPEndpoint> epList = localEndpoints.get(endpointKey);
+ String localHost = resampleLocalHost();
+
+ if (epList == null) {
+ /*
+ * Create new endpoint list.
+ */
+ ep = new TCPEndpoint(localHost, port, csf, ssf);
+ epList = new LinkedList<TCPEndpoint>();
+ epList.add(ep);
+ ep.listenPort = port;
+ ep.transport = new TCPTransport(epList);
+ localEndpoints.put(endpointKey, epList);
+
+ if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+ TCPTransport.tcpLog.log(Log.BRIEF,
+ "created local endpoint for socket factory " + ssf +
+ " on port " + port);
+ }
+ } else {
+ synchronized (epList) {
+ ep = epList.getLast();
+ String lastHost = ep.host;
+ int lastPort = ep.port;
+ TCPTransport lastTransport = ep.transport;
+ // assert (localHost == null ^ lastHost != null)
+ if (localHost != null && !localHost.equals(lastHost)) {
+ /*
+ * Hostname has been updated; add updated endpoint
+ * to list.
+ */
+ if (lastPort != 0) {
+ /*
+ * Remove outdated endpoints only if the
+ * port has already been set on those endpoints.
+ */
+ epList.clear();
+ }
+ ep = new TCPEndpoint(localHost, lastPort, csf, ssf);
+ ep.listenPort = port;
+ ep.transport = lastTransport;
+ epList.add(ep);
+ }
+ }
+ }
+ }
+
+ return ep;
+ }
+
+ /**
+ * Resamples the local hostname and returns the possibly-updated
+ * local hostname.
+ */
+ private static String resampleLocalHost() {
+
+ String hostnameProperty = getHostnameProperty();
+
+ synchronized (localEndpoints) {
+ // assert(localHostKnown ^ (localHost == null))
+
+ if (hostnameProperty != null) {
+ if (!localHostKnown) {
+ /*
+ * If the local hostname is unknown, update ALL
+ * existing endpoints with the new hostname.
+ */
+ setLocalHost(hostnameProperty);
+ } else if (!hostnameProperty.equals(localHost)) {
+ /*
+ * Only update the localHost field for reference
+ * in future endpoint creation.
+ */
+ localHost = hostnameProperty;
+
+ if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+ TCPTransport.tcpLog.log(Log.BRIEF,
+ "updated local hostname to: " + localHost);
+ }
+ }
+ }
+ return localHost;
+ }
+ }
+
+ /**
+ * Set the local host name, if currently unknown.
+ */
+ static void setLocalHost(String host) {
+ // assert (host != null)
+
+ synchronized (localEndpoints) {
+ /*
+ * If host is not known, change the host field of ALL
+ * the local endpoints.
+ */
+ if (!localHostKnown) {
+ localHost = host;
+ localHostKnown = true;
+
+ if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+ TCPTransport.tcpLog.log(Log.BRIEF,
+ "local host set to " + host);
+ }
+ for (LinkedList<TCPEndpoint> epList : localEndpoints.values())
+ {
+ synchronized (epList) {
+ for (TCPEndpoint ep : epList) {
+ ep.host = host;
+ }
+ }
+ }
+ }
+ }
+ }
+
+ /**
+ * Set the port of the (shared) default endpoint object.
+ * When first created, it contains port 0 because the transport
+ * hasn't tried to listen to get assigned a port, or if listening
+ * failed, a port hasn't been assigned from the server.
+ */
+ static void setDefaultPort(int port, RMIClientSocketFactory csf,
+ RMIServerSocketFactory ssf)
+ {
+ TCPEndpoint endpointKey = new TCPEndpoint(null, 0, csf, ssf);
+
+ synchronized (localEndpoints) {
+ LinkedList<TCPEndpoint> epList = localEndpoints.get(endpointKey);
+
+ synchronized (epList) {
+ int size = epList.size();
+ TCPEndpoint lastEp = epList.getLast();
+
+ for (TCPEndpoint ep : epList) {
+ ep.port = port;
+ }
+ if (size > 1) {
+ /*
+ * Remove all but the last element of the list
+ * (which contains the most recent hostname).
+ */
+ epList.clear();
+ epList.add(lastEp);
+ }
+ }
+
+ /*
+ * Allow future exports to use the actual bound port
+ * explicitly (see 6269166).
+ */
+ TCPEndpoint newEndpointKey = new TCPEndpoint(null, port, csf, ssf);
+ localEndpoints.put(newEndpointKey, epList);
+
+ if (TCPTransport.tcpLog.isLoggable(Log.BRIEF)) {
+ TCPTransport.tcpLog.log(Log.BRIEF,
+ "default port for server socket factory " + ssf +
+ " and client socket factory " + csf +
+ " set to " + port);
+ }
+ }
+ }
+
+ /**
+ * Returns transport for making connections to remote endpoints;
+ * (here, the default transport at port 0 is used).
+ */
+ public Transport getOutboundTransport() {
+ TCPEndpoint localEndpoint = getLocalEndpoint(0, null, null);
+ return localEndpoint.transport;
+ }
+
+ /**
+ * Returns the current list of known transports.
+ * The returned list is an unshared collection of Transports,
+ * including all transports which may have channels to remote
+ * endpoints.
+ */
+ private static Collection<TCPTransport> allKnownTransports() {
+ // Loop through local endpoints, getting the transport of each one.
+ Set<TCPTransport> s;
+ synchronized (localEndpoints) {
+ // presize s to number of localEndpoints
+ s = new HashSet<TCPTransport>(localEndpoints.size());
+ for (LinkedList<TCPEndpoint> epList : localEndpoints.values()) {
+ /*
+ * Each local endpoint has its transport added to s.
+ * Note: the transport is the same for all endpoints
+ * in the list, so it is okay to pick any one of them.
+ */
+ TCPEndpoint ep = epList.getFirst();
+ s.add(ep.transport);
+ }
+ }
+ return s;
+ }
+
+ /**
+ * Release idle outbound connections to reduce demand on I/O resources.
+ * All transports are asked to release excess connections.
+ */
+ public static void shedConnectionCaches() {
+ for (TCPTransport transport : allKnownTransports()) {
+ transport.shedConnectionCaches();
+ }
+ }
+
+ /**
+ * Export the object to accept incoming calls.
+ */
+ public void exportObject(Target target) throws RemoteException {
+ transport.exportObject(target);
+ }
+
+ /**
+ * Returns a channel for this (remote) endpoint.
+ */
+ public Channel getChannel() {
+ return getOutboundTransport().getChannel(this);
+ }
+
+ /**
+ * Returns address for endpoint
+ */
+ public String getHost() {
+ return host;
+ }
+
+ /**
+ * Returns the port for this endpoint. If this endpoint was
+ * created as a server endpoint (using getLocalEndpoint) for a
+ * default/anonymous port and its inbound transport has started
+ * listening, this method returns (instead of zero) the actual
+ * bound port suitable for passing to clients.
+ **/
+ public int getPort() {
+ return port;
+ }
+
+ /**
+ * Returns the port that this endpoint's inbound transport listens
+ * on, if this endpoint was created as a server endpoint (using
+ * getLocalEndpoint). If this endpoint was created for the
+ * default/anonymous port, then this method returns zero even if
+ * the transport has started listening.
+ **/
+ public int getListenPort() {
+ return listenPort;
+ }
+
+ /**
+ * Returns the transport for incoming connections to this
+ * endpoint, if this endpoint was created as a server endpoint
+ * (using getLocalEndpoint).
+ **/
+ public Transport getInboundTransport() {
+ return transport;
+ }
+
+ /**
+ * Get the client socket factory associated with this endpoint.
+ */
+ public RMIClientSocketFactory getClientSocketFactory() {
+ return csf;
+ }
+
+ /**
+ * Get the server socket factory associated with this endpoint.
+ */
+ public RMIServerSocketFactory getServerSocketFactory() {
+ return ssf;
+ }
+
+ /**
+ * Return string representation for endpoint.
+ */
+ public String toString() {
+ return "[" + host + ":" + port +
+ (ssf != null ? "," + ssf : "") +
+ (csf != null ? "," + csf : "") +
+ "]";
+ }
+
+ public int hashCode() {
+ return port;
+ }
+
+ public boolean equals(Object obj) {
+ if ((obj != null) && (obj instanceof TCPEndpoint)) {
+ TCPEndpoint ep = (TCPEndpoint) obj;
+ if (port != ep.port || !host.equals(ep.host))
+ return false;
+ if (((csf == null) ^ (ep.csf == null)) ||
+ ((ssf == null) ^ (ep.ssf == null)))
+ return false;
+ /*
+ * Fix for 4254510: perform socket factory *class* equality check
+ * before socket factory equality check to avoid passing
+ * a potentially naughty socket factory to this endpoint's
+ * {client,server} socket factory equals method.
+ */
+ if ((csf != null) &&
+ !(csf.getClass() == ep.csf.getClass() && csf.equals(ep.csf)))
+ return false;
+ if ((ssf != null) &&
+ !(ssf.getClass() == ep.ssf.getClass() && ssf.equals(ep.ssf)))
+ return false;
+ return true;
+ } else {
+ return false;
+ }
+ }
+
+ /* codes for the self-describing formats of wire representation */
+ private static final int FORMAT_HOST_PORT = 0;
+ private static final int FORMAT_HOST_PORT_FACTORY = 1;
+
+ /**
+ * Write endpoint to output stream.
+ */
+ public void write(ObjectOutput out) throws IOException {
+ if (csf == null) {
+ out.writeByte(FORMAT_HOST_PORT);
+ out.writeUTF(host);
+ out.writeInt(port);
+ } else {
+ out.writeByte(FORMAT_HOST_PORT_FACTORY);
+ out.writeUTF(host);
+ out.writeInt(port);
+ out.writeObject(csf);
+ }
+ }
+
+ /**
+ * Get the endpoint from the input stream.
+ * @param in the input stream
+ * @exception IOException If id could not be read (due to stream failure)
+ */
+ public static TCPEndpoint read(ObjectInput in)
+ throws IOException, ClassNotFoundException
+ {
+ String host;
+ int port;
+ RMIClientSocketFactory csf = null;
+
+ byte format = in.readByte();
+ switch (format) {
+ case FORMAT_HOST_PORT:
+ host = in.readUTF();
+ port = in.readInt();
+ break;
+
+ case FORMAT_HOST_PORT_FACTORY:
+ host = in.readUTF();
+ port = in.readInt();
+ csf = (RMIClientSocketFactory) in.readObject();
+ break;
+
+ default:
+ throw new IOException("invalid endpoint format");
+ }
+ return new TCPEndpoint(host, port, csf, null);
+ }
+
+ /**
+ * Write endpoint to output stream in older format used by
+ * UnicastRef for JDK1.1 compatibility.
+ */
+ public void writeHostPortFormat(DataOutput out) throws IOException {
+ if (csf != null) {
+ throw new InternalError("TCPEndpoint.writeHostPortFormat: " +
+ "called for endpoint with non-null socket factory");
+ }
+ out.writeUTF(host);
+ out.writeInt(port);
+ }
+
+ /**
+ * Create a new endpoint from input stream data.
+ * @param in the input stream
+ */
+ public static TCPEndpoint readHostPortFormat(DataInput in)
+ throws IOException
+ {
+ String host = in.readUTF();
+ int port = in.readInt();
+ return new TCPEndpoint(host, port);
+ }
+
+ private static RMISocketFactory chooseFactory() {
+ RMISocketFactory sf = RMISocketFactory.getSocketFactory();
+ if (sf == null) {
+ sf = TCPTransport.defaultSocketFactory;
+ }
+ return sf;
+ }
+
+ /**
+ * Open and return new client socket connection to endpoint.
+ */
+ Socket newSocket() throws RemoteException {
+ if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
+ TCPTransport.tcpLog.log(Log.VERBOSE,
+ "opening socket to " + this);
+ }
+
+ Socket socket;
+
+ try {
+ RMIClientSocketFactory clientFactory = csf;
+ if (clientFactory == null) {
+ clientFactory = chooseFactory();
+ }
+ socket = clientFactory.createSocket(host, port);
+
+ } catch (java.net.UnknownHostException e) {
+ throw new java.rmi.UnknownHostException(
+ "Unknown host: " + host, e);
+ } catch (java.net.ConnectException e) {
+ throw new java.rmi.ConnectException(
+ "Connection refused to host: " + host, e);
+ } catch (IOException e) {
+ // We might have simply run out of file descriptors
+ try {
+ TCPEndpoint.shedConnectionCaches();
+ // REMIND: should we retry createSocket?
+ } catch (OutOfMemoryError mem) {
+ // don't quit if out of memory
+ } catch (Exception ex) {
+ // don't quit if shed fails non-catastrophically
+ }
+
+ throw new ConnectIOException("Exception creating connection to: " +
+ host, e);
+ }
+
+ // set socket to disable Nagle's algorithm (always send immediately)
+ // TBD: should this be left up to socket factory instead?
+ try {
+ socket.setTcpNoDelay(true);
+ } catch (Exception e) {
+ // if we fail to set this, ignore and proceed anyway
+ }
+
+ // fix 4187495: explicitly set SO_KEEPALIVE to prevent client hangs
+ try {
+ socket.setKeepAlive(true);
+ } catch (Exception e) {
+ // ignore and proceed
+ }
+
+ return socket;
+ }
+
+ /**
+ * Return new server socket to listen for connections on this endpoint.
+ */
+ ServerSocket newServerSocket() throws IOException {
+ if (TCPTransport.tcpLog.isLoggable(Log.VERBOSE)) {
+ TCPTransport.tcpLog.log(Log.VERBOSE,
+ "creating server socket on " + this);
+ }
+
+ RMIServerSocketFactory serverFactory = ssf;
+ if (serverFactory == null) {
+ serverFactory = chooseFactory();
+ }
+ ServerSocket server = serverFactory.createServerSocket(listenPort);
+
+ // if we listened on an anonymous port, set the default port
+ // (for this socket factory)
+ if (listenPort == 0)
+ setDefaultPort(server.getLocalPort(), csf, ssf);
+
+ return server;
+ }
+
+ /**
+ * The class FQDN encapsulates a routine that makes a best effort
+ * attempt to retrieve the fully qualified domain name of the local
+ * host.
+ *
+ * @author Laird Dornin
+ */
+ private static class FQDN implements Runnable {
+
+ /**
+ * strings in which we can store discovered fqdn
+ */
+ private String reverseLookup;
+
+ private String hostAddress;
+
+ private FQDN(String hostAddress) {
+ this.hostAddress = hostAddress;
+ }
+
+ /**
+ * Do our best to obtain a fully qualified hostname for the local
+ * host. Perform the following steps to get a localhostname:
+ *
+ * 1. InetAddress.getLocalHost().getHostName() - if contains
+ * '.' use as FQDN
+ * 2. if no '.' query name service for FQDN in a thread
+ * Note: We query the name service for an FQDN by creating
+ * an InetAddress via a stringified copy of the local ip
+ * address; this creates an InetAddress with a null hostname.
+ * Asking for the hostname of this InetAddress causes a name
+ * service lookup.
+ *
+ * 3. if name service takes too long to return, use ip address
+ * 4. if name service returns but response contains no '.'
+ * default to ipaddress.
+ */
+ static String attemptFQDN(InetAddress localAddr)
+ throws java.net.UnknownHostException
+ {
+
+ String hostName = localAddr.getHostName();
+
+ if (hostName.indexOf('.') < 0 ) {
+
+ String hostAddress = localAddr.getHostAddress();
+ FQDN f = new FQDN(hostAddress);
+
+ int nameServiceTimeOut =
+ TCPEndpoint.getInt("sun.rmi.transport.tcp.localHostNameTimeOut",
+ 10000);
+
+ try {
+ synchronized(f) {
+ f.getFQDN();
+
+ /* wait to obtain an FQDN */
+ f.wait(nameServiceTimeOut);
+ }
+ } catch (InterruptedException e) {
+ /* propagate the exception to the caller */
+ Thread.currentThread().interrupt();
+ }
+ hostName = f.getHost();
+
+ if ((hostName == null) || (hostName.equals(""))
+ || (hostName.indexOf('.') < 0 )) {
+
+ hostName = hostAddress;
+ }
+ }
+ return hostName;
+ }
+
+ /**
+ * Method that that will start a thread to wait to retrieve a
+ * fully qualified domain name from a name service. The spawned
+ * thread may never return but we have marked it as a daemon so the vm
+ * will terminate appropriately.
+ */
+ private void getFQDN() {
+
+ /* FQDN finder will run in RMI threadgroup. */
+ Thread t = AccessController.doPrivileged(
+ new NewThreadAction(FQDN.this, "FQDN Finder", true));
+ t.start();
+ }
+
+ private synchronized String getHost() {
+ return reverseLookup;
+ }
+
+ /**
+ * thread to query a name service for the fqdn of this host.
+ */
+ public void run() {
+
+ String name = null;
+
+ try {
+ name = InetAddress.getByName(hostAddress).getHostName();
+ } catch (java.net.UnknownHostException e) {
+ } finally {
+ synchronized(this) {
+ reverseLookup = name;
+ this.notify();
+ }
+ }
+ }
+ }
+}