Source for gnu.java.rmi.server.UnicastConnectionManager

   1: /* UnicastConnectionManager.java --
   2:    Copyright (c) 1996, 1997, 1998, 1999, 2002, 2004
   3:    Free Software Foundation, Inc.
   4: 
   5: This file is part of GNU Classpath.
   6: 
   7: GNU Classpath is free software; you can redistribute it and/or modify
   8: it under the terms of the GNU General Public License as published by
   9: the Free Software Foundation; either version 2, or (at your option)
  10: any later version.
  11: 
  12: GNU Classpath is distributed in the hope that it will be useful, but
  13: WITHOUT ANY WARRANTY; without even the implied warranty of
  14: MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
  15: General Public License for more details.
  16: 
  17: You should have received a copy of the GNU General Public License
  18: along with GNU Classpath; see the file COPYING.  If not, write to the
  19: Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  20: 02110-1301 USA.
  21: 
  22: Linking this library statically or dynamically with other modules is
  23: making a combined work based on this library.  Thus, the terms and
  24: conditions of the GNU General Public License cover the whole
  25: combination.
  26: 
  27: As a special exception, the copyright holders of this library give you
  28: permission to link this library with independent modules to produce an
  29: executable, regardless of the license terms of these independent
  30: modules, and to copy and distribute the resulting executable under
  31: terms of your choice, provided that you also meet, for each linked
  32: independent module, the terms and conditions of the license of that
  33: module.  An independent module is a module which is not derived from
  34: or based on this library.  If you modify this library, you may extend
  35: this exception to your version of the library, but you are not
  36: obligated to do so.  If you do not wish to do so, delete this
  37: exception statement from your version. */
  38: 
  39: 
  40: package gnu.java.rmi.server;
  41: 
  42: import java.io.IOException;
  43: import java.io.ObjectInput;
  44: import java.io.ObjectOutput;
  45: import java.net.InetAddress;
  46: import java.net.ServerSocket;
  47: import java.net.Socket;
  48: import java.net.UnknownHostException;
  49: import java.rmi.RemoteException;
  50: import java.rmi.server.RMIClientSocketFactory;
  51: import java.rmi.server.RMIServerSocketFactory;
  52: import java.rmi.server.RMISocketFactory;
  53: import java.util.ArrayList;
  54: import java.util.ConcurrentModificationException;
  55: import java.util.Hashtable;
  56: import java.util.Iterator;
  57: 
  58: public class UnicastConnectionManager
  59:         implements Runnable, ProtocolConstants {
  60: 
  61: private static String localhost;
  62: // use different maps for server/client type UnicastConnectionManager
  63: private static Hashtable servers = new Hashtable();
  64: // Package-private to avoid trampolines.
  65: static Hashtable clients = new Hashtable();
  66: ArrayList connections; //client connection pool
  67: 
  68: // make serverThread volatile for poll
  69: private volatile Thread serverThread;
  70: private ServerSocket ssock;
  71: String serverName;
  72: int serverPort;
  73: 
  74: // Package-private to avoid a trampoline.
  75: static Thread scavenger;
  76: 
  77: // If client and server are in the same VM, serverobj represents server
  78: Object serverobj;
  79: 
  80: private static RMISocketFactory defaultSocketFactory = RMISocketFactory.getSocketFactory();
  81: private RMIServerSocketFactory serverFactory;
  82: private RMIClientSocketFactory clientFactory;
  83: 
  84: // The following is for debug
  85: private static int ncsock = 0;    //count of client socket
  86: private static int nssock = 0;    //count of server socket
  87: private static int ncmanager = 0; //count of client manager
  88: private static int nsmanager = 0; //count of server manager
  89: 
  90: private static final boolean debug = false;
  91: 
  92: private static final Object GLOBAL_LOCK = new Object();
  93: 
  94: static {
  95:         try {
  96:                 //Use host address instead of host name to avoid name resolving issues
  97:                 //localhost = InetAddress.getLocalHost().getHostName();
  98:                 localhost = InetAddress.getLocalHost().getHostAddress();
  99:         }
 100:         catch (UnknownHostException _) {
 101:                 localhost = "localhost";
 102:         }
 103: 
 104: 
 105: }
 106: 
 107: //Only one scavenger thread running globally
 108: private static void startScavenger(){
 109:     scavenger = new Thread(new Runnable(){
 110:         public void run(){
 111:             if (debug) System.out.println("************* start scavenger.");
 112:             boolean liveon = true;
 113:             while (liveon){
 114:                 // Sleep for the expire timeout
 115:                 try{
 116:                     Thread.sleep(UnicastConnection.CONNECTION_TIMEOUT);
 117:                 }catch(InterruptedException _ie){
 118:                     break;
 119:                 }
 120:                 liveon = false;
 121:                 // Scavenge all clients' connections that're expired
 122:                 Iterator iter = clients.values().iterator();
 123:                 long l = System.currentTimeMillis();
 124:                 try{
 125:                     while(iter.hasNext()){
 126:                         UnicastConnectionManager man = (UnicastConnectionManager)iter.next();
 127:                         ArrayList conns = man.connections;
 128:                         synchronized(conns) { // is the lock a little coarser?
 129:                             for (int last = conns.size() - 1;
 130:                                  last >= 0;
 131:                                  --last)
 132:                             {
 133:                                 UnicastConnection conn = (UnicastConnection)conns.get(last);
 134:                                 if (UnicastConnection.isExpired(conn, l)){
 135:                                     conns.remove(last);
 136:                                     conn.disconnect();
 137:                                     conn = null;
 138:                                 }else
 139:                                     liveon = true; //there're still live connections
 140:                             }
 141:                         }
 142:                     }
 143:                 }catch(ConcurrentModificationException cme) {
 144:                     // handle it lazily
 145:                     liveon = true;
 146:                 }
 147:             }
 148:             scavenger = null;
 149:             if (debug) System.out.println("************* exit scavenger.");
 150:         }
 151:     });
 152:     // As it is used for client connection, we may put this thread
 153:     // in daemon state to prevent the VM from blocking when exiting.
 154:     scavenger.setDaemon(true);
 155:     scavenger.start();
 156: }
 157: 
 158: /**
 159:   * Client UnicastConnectionManager constructor
 160:   */
 161: private UnicastConnectionManager(String host, int port, RMIClientSocketFactory csf) {
 162:         ssock = null;
 163:         serverName = host;
 164:         serverPort = port;
 165:         serverFactory = null;
 166:         clientFactory = csf;
 167:     connections = new ArrayList();
 168: }
 169: 
 170: /**
 171:   * Server UnicastConnectionManager constructor
 172:   */
 173: private UnicastConnectionManager(int port, RMIServerSocketFactory ssf) throws RemoteException {
 174: 
 175:         try {
 176:                 ssock = ssf.createServerSocket(port);
 177:                 serverPort = ssock.getLocalPort();
 178:         }
 179:         catch (IOException ioex) {
 180:                 ssock = null;
 181:                 serverPort = 0;
 182:                 throw new java.rmi.server.ExportException("can not create Server Socket on port " + port,ioex);
 183:         }
 184:         // Note that for compatibility the serverName is "localhost",
 185:         // not UnicastConnectionManager.localhost, which is the name
 186:         // of the local box.  A server listening on localhost:port is
 187:         // listening on the loopback interface, 127.0.0.1, but
 188:         // UnicastConnectionManager.localhost is an externally
 189:         // accessible IP address.
 190:         serverName = "localhost";
 191:         serverFactory = ssf;
 192:         clientFactory = null;
 193: }
 194: 
 195: /**
 196:  * Return a client connection manager which will connect to the given
 197:  * host/port.
 198:  */
 199: public static synchronized UnicastConnectionManager getInstance(String host, int port, RMIClientSocketFactory csf) {
 200: //System.out.println("getInstance: " + host + "," + port + "," + csf);
 201:         if (csf == null) {
 202:         csf = defaultSocketFactory;
 203:         }
 204:         // change host name to host address to avoid name resolving issues
 205:         try{
 206:         host = InetAddress.getByName(host).getHostAddress();
 207:     }catch(Exception _){}
 208: 
 209:         TripleKey key = new TripleKey(host, port, csf);
 210:         UnicastConnectionManager man = (UnicastConnectionManager)clients.get(key);
 211:         if (man == null) {
 212:                 man = new UnicastConnectionManager(host, port, csf);
 213:         if (debug) {
 214:             ncmanager++;
 215:             System.out.println("\n\n ====== " + ncmanager + " client managers.\n\n");
 216:         }
 217:                 clients.put(key, man);
 218: 
 219:         // Detect if client and server are in the same VM, i.e., their keys are equal
 220:         UnicastConnectionManager svrman = (UnicastConnectionManager)servers.get(key);
 221:         if(svrman != null){ // server and client are in the same VM
 222:             man.serverobj = svrman.serverobj;
 223:         }
 224:         }
 225:         return (man);
 226: }
 227: 
 228: /**
 229:  * Return a server connection manager which will accept connection on the
 230:  * given port.
 231:  */
 232: public static synchronized UnicastConnectionManager getInstance(int port, RMIServerSocketFactory ssf) throws RemoteException {
 233: //System.out.println("getInstance: " + port + "," + ssf);
 234:         if (ssf == null) {
 235:         ssf = defaultSocketFactory;
 236:         }
 237:         TripleKey key = new TripleKey(localhost, port, ssf);
 238:         UnicastConnectionManager man = (UnicastConnectionManager)servers.get(key);
 239:         if (man == null) {
 240:                 man = new UnicastConnectionManager(port, ssf);
 241:         if (debug) {
 242:             nsmanager++;
 243:             System.out.println("\n\n ****** " + nsmanager + " server managers.\n\n");
 244:         }
 245:                 // The provided port might not be the set port.
 246:                 key.port = man.serverPort;
 247:                 servers.put(key, man);
 248:         }
 249:         return (man);
 250: }
 251: 
 252: /**
 253:  * Get a connection from this manager.
 254:  */
 255: public UnicastConnection getConnection() throws IOException {
 256:         if (ssock == null) {
 257:                 return (getClientConnection());
 258:         }
 259:         else {
 260:                 return (getServerConnection());
 261:         }
 262: }
 263: 
 264: /**
 265:  * Accept a connection to this server.
 266:  */
 267: private UnicastConnection getServerConnection() throws IOException {
 268:         Socket sock = ssock.accept();
 269:     sock.setTcpNoDelay(true); //??
 270:         UnicastConnection conn = new UnicastConnection(this, sock);
 271:         conn.acceptConnection();
 272:     if (debug){
 273:         nssock++;
 274:         System.out.println("\n\n ****** " + nssock + " server socks.\n\n");
 275:     }
 276:     //System.out.println("Server connection " + sock);
 277:         return (conn);
 278: }
 279: 
 280: /**
 281:  * Make a conection from this client to the server.
 282:  */
 283: private UnicastConnection getClientConnection() throws IOException {
 284:     ArrayList conns = connections;
 285:     UnicastConnection conn;
 286: 
 287:     synchronized(conns) {
 288:         int nconn = conns.size() - 1;
 289: 
 290:         // if there're free connections in connection pool
 291:         if(nconn >= 0) {
 292:             conn = (UnicastConnection)conns.get(nconn);
 293:             //Should we check if conn is alive using Ping??
 294:             conns.remove(nconn);
 295: 
 296:             // Check if the connection is already expired
 297:             long l = System.currentTimeMillis();
 298:             if (!UnicastConnection.isExpired(conn, l)){
 299:                 return conn;
 300:             }else {
 301:                 conn.disconnect();
 302:                 conn = null;
 303:             }
 304:         }
 305:     }
 306: 
 307:         Socket sock = clientFactory.createSocket(serverName, serverPort);
 308:     conn = new UnicastConnection(this, sock);
 309:         conn.makeConnection(DEFAULT_PROTOCOL);
 310: 
 311:     if (debug) {
 312:         ncsock++;
 313:         System.out.println("\n\n ====== " + ncsock + " client socks.\n\n");
 314:     }
 315: 
 316:         return (conn);
 317: }
 318: 
 319: /**
 320:  * Get the string representation, describing the connection.
 321:  */
 322: public String toString()
 323: {
 324:   return serverName+":"+serverPort+" ("+serverobj+")";
 325: }
 326: 
 327: /**
 328:  * Discard a connection when we're done with it - maybe it can be
 329:  * recycled.
 330:  */
 331: public void discardConnection(UnicastConnection conn) {
 332: //System.out.println("Discarding connection " + conn);
 333:     //conn.disconnect();
 334:     if (ssock != null) //server connection
 335:         conn.disconnect();
 336:     else {
 337:         // To client connection, we'd like to return back to pool
 338:         UnicastConnection.resetTime(conn);
 339:         //Ensure there're only one scavenger globally
 340:         synchronized(GLOBAL_LOCK) {
 341:             connections.add(conn); //borrow this lock to garantee thread safety
 342:             if (scavenger == null)
 343:                 startScavenger();
 344:         }
 345:     }
 346: }
 347: 
 348: /**
 349:  * Start a server on this manager if it's a server socket and we've not
 350:  * already got one running.
 351:  */
 352: public void startServer() {
 353:         synchronized(this) {
 354:                 if (ssock == null || serverThread != null) {
 355:                         return;
 356:                 }
 357:                 serverThread = new Thread(this);
 358:         // The following is not necessary when java.lang.Thread's constructor do this.
 359:         // serverThread.setContextClassLoader(Thread.currentThread().getContextClassLoader());
 360:         }
 361:         serverThread.start();
 362: }
 363: 
 364: /**
 365:  * Stop a server on this manager
 366:  */
 367: public void stopServer() {
 368:     synchronized(this) {
 369:         if(serverThread != null){
 370:             serverThread = null;
 371:             try{
 372:                 ssock.close();
 373:             }catch(Exception _){}
 374:         }
 375:     }
 376: }
 377: 
 378: /**
 379:  * Server thread for connection manager.
 380:  */
 381: public void run() {
 382:         for (;serverThread != null;) { // if serverThread==null, then exit thread
 383:                 try {
 384: //System.out.println("Waiting for connection on " + serverPort);
 385:                         UnicastConnection conn = getServerConnection();
 386: 
 387:                         // get address of remote host for the RMIIncomingThread object
 388:                         String remoteHost = null;
 389:                         if (conn.sock != null) {
 390:                                 remoteHost = conn.sock.getInetAddress().getHostAddress();
 391:                         }
 392: 
 393:                         // use a thread pool to improve performance
 394:             //ConnectionRunnerPool.dispatchConnection(conn);
 395:             (new RMIIncomingThread(conn, remoteHost)).start();
 396: //         (new Thread(conn)).start();
 397:                 }
 398:                 catch (Exception e) {
 399:             e.printStackTrace();
 400:                 }
 401:         }
 402: }
 403: 
 404: /**
 405:  * Serialization routine.
 406:  */
 407: void write(ObjectOutput out) throws IOException {
 408:         out.writeUTF(serverName);
 409:         out.writeInt(serverPort);
 410: }
 411: 
 412: /**
 413:  * Serialization routine.
 414:  */
 415: static UnicastConnectionManager read(ObjectInput in) throws IOException {
 416:         String host = in.readUTF();
 417:         int port = in.readInt();
 418:         //RMIClientSocketFactory csf = ((RMIObjectInputStream)in).manager.clientFactory;
 419:         //return (getInstance(host, port, csf));
 420:         return (getInstance(host, port, null));
 421: }
 422: 
 423: }
 424: 
 425: /**
 426:  * This is use as the hashkey for the client/server connections.
 427:  */
 428: class TripleKey {
 429: 
 430: String host;
 431: int port;
 432: Object other;
 433: 
 434: TripleKey(String host, int port, Object other) {
 435:         this.host = host;
 436:         this.port = port;
 437:         this.other = other;
 438: }
 439: 
 440: /**
 441:  * Hash code just include the host and other - we ignore the port since
 442:  * this has unusual matching behaviour.
 443:  */
 444: public int hashCode() {
 445:         return (host.hashCode() ^ other.hashCode());
 446: }
 447: 
 448: public boolean equals(Object obj) {
 449:         if (obj instanceof TripleKey) {
 450:                 TripleKey other = (TripleKey)obj;
 451:                 if (this.host.equals(other.host) &&
 452:                     this.other == other.other &&
 453:             (this.port == other.port /* || this.port == 0 || other.port == 0*/)) {
 454:                         return (true);
 455:                 }
 456:         }
 457:         return (false);
 458: }
 459: 
 460:   /**
 461:    * Get the string representation, describing the connection.
 462:    */
 463:   public String toString()
 464:   {
 465:     return host+":"+port+" ("+other+")";
 466:   }
 467: 
 468: }