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:     serverName = localhost;
 185:     serverFactory = ssf;
 186:     clientFactory = null;
 187: }
 188: 
 189: /**
 190:  * Return a client connection manager which will connect to the given
 191:  * host/port.
 192:  */
 193: public static synchronized UnicastConnectionManager getInstance(String host, int port, RMIClientSocketFactory csf) {
 194: //System.out.println("getInstance: " + host + "," + port + "," + csf);
 195:     if (csf == null) {
 196:         csf = defaultSocketFactory;
 197:     }
 198:     // change host name to host address to avoid name resolving issues
 199:     try{
 200:         host = InetAddress.getByName(host).getHostAddress();
 201:     }catch(Exception _){}
 202:     
 203:     TripleKey key = new TripleKey(host, port, csf);
 204:     UnicastConnectionManager man = (UnicastConnectionManager)clients.get(key);
 205:     if (man == null) {
 206:         man = new UnicastConnectionManager(host, port, csf);
 207:         if (debug) {
 208:             ncmanager++;
 209:             System.out.println("\n\n ====== " + ncmanager + " client managers.\n\n");
 210:         }
 211:         clients.put(key, man);
 212:         
 213:         // Detect if client and server are in the same VM, i.e., their keys are equal
 214:         UnicastConnectionManager svrman = (UnicastConnectionManager)servers.get(key);
 215:         if(svrman != null){ // server and client are in the same VM
 216:             man.serverobj = svrman.serverobj;
 217:         }
 218:     }
 219:     return (man);
 220: }
 221: 
 222: /**
 223:  * Return a server connection manager which will accept connection on the
 224:  * given port.
 225:  */
 226: public static synchronized UnicastConnectionManager getInstance(int port, RMIServerSocketFactory ssf) throws RemoteException {
 227: //System.out.println("getInstance: " + port + "," + ssf);
 228:     if (ssf == null) {
 229:         ssf = defaultSocketFactory;
 230:     }
 231:     TripleKey key = new TripleKey(localhost, port, ssf);
 232:     UnicastConnectionManager man = (UnicastConnectionManager)servers.get(key);
 233:     if (man == null) {
 234:         man = new UnicastConnectionManager(port, ssf);
 235:         if (debug) {
 236:             nsmanager++;
 237:             System.out.println("\n\n ****** " + nsmanager + " server managers.\n\n");
 238:         }
 239:         // The provided port might not be the set port.
 240:         key.port = man.serverPort;
 241:         servers.put(key, man);
 242:     }
 243:     return (man);
 244: }
 245: 
 246: /**
 247:  * Get a connection from this manager.
 248:  */
 249: public UnicastConnection getConnection() throws IOException {
 250:     if (ssock == null) {
 251:         return (getClientConnection());
 252:     }
 253:     else {
 254:         return (getServerConnection());
 255:     }
 256: }
 257: 
 258: /**
 259:  * Accept a connection to this server.
 260:  */
 261: private UnicastConnection getServerConnection() throws IOException {
 262:     Socket sock = ssock.accept();
 263:     sock.setTcpNoDelay(true); //??
 264:     UnicastConnection conn = new UnicastConnection(this, sock);
 265:     conn.acceptConnection();
 266:     if (debug){
 267:         nssock++;
 268:         System.out.println("\n\n ****** " + nssock + " server socks.\n\n");
 269:     }
 270:     //System.out.println("Server connection " + sock);
 271:     return (conn);
 272: }
 273: 
 274: /**
 275:  * Make a conection from this client to the server.
 276:  */
 277: private UnicastConnection getClientConnection() throws IOException {
 278:     ArrayList conns = connections;
 279:     UnicastConnection conn;
 280:     
 281:     synchronized(conns) {
 282:         int nconn = conns.size() - 1;
 283:     
 284:         // if there're free connections in connection pool
 285:         if(nconn >= 0) {
 286:             conn = (UnicastConnection)conns.get(nconn);
 287:             //Should we check if conn is alive using Ping??
 288:             conns.remove(nconn);
 289:             
 290:             // Check if the connection is already expired
 291:             long l = System.currentTimeMillis();
 292:             if (!UnicastConnection.isExpired(conn, l)){
 293:                 return conn;
 294:             }else {
 295:                 conn.disconnect();
 296:                 conn = null;   
 297:             }
 298:         }
 299:     }
 300:     
 301:     Socket sock = clientFactory.createSocket(serverName, serverPort);
 302:     conn = new UnicastConnection(this, sock);
 303:     conn.makeConnection(DEFAULT_PROTOCOL);
 304:     
 305:     if (debug) {
 306:         ncsock++;
 307:         System.out.println("\n\n ====== " + ncsock + " client socks.\n\n");
 308:     }
 309: 
 310:     return (conn);
 311: }
 312: 
 313: /**
 314:  * Discard a connection when we're done with it - maybe it can be
 315:  * recycled.
 316:  */
 317: public void discardConnection(UnicastConnection conn) {
 318: //System.out.println("Discarding connection " + conn);
 319:     //conn.disconnect();
 320:     if (ssock != null) //server connection
 321:     conn.disconnect();
 322:     else {
 323:         // To client connection, we'd like to return back to pool
 324:         UnicastConnection.resetTime(conn);
 325:         //Ensure there're only one scavenger globally
 326:         synchronized(GLOBAL_LOCK) {
 327:             connections.add(conn); //borrow this lock to garantee thread safety 
 328:             if (scavenger == null)
 329:                 startScavenger();
 330:         }
 331:     }
 332: }
 333: 
 334: /**
 335:  * Start a server on this manager if it's a server socket and we've not
 336:  * already got one running.
 337:  */
 338: public void startServer() {
 339:     synchronized(this) {
 340:         if (ssock == null || serverThread != null) {
 341:             return;
 342:         }
 343:         serverThread = new Thread(this);
 344:         // The following is not necessary when java.lang.Thread's constructor do this.
 345:         // serverThread.setContextClassLoader(Thread.currentThread().getContextClassLoader());
 346:     }
 347:     serverThread.start();
 348: }
 349: 
 350: /**
 351:  * Stop a server on this manager
 352:  */
 353: public void stopServer() {
 354:     synchronized(this) {
 355:         if(serverThread != null){
 356:             serverThread = null;
 357:             try{
 358:                 ssock.close();
 359:             }catch(Exception _){}
 360:         }
 361:     }
 362: }
 363: 
 364: /**
 365:  * Server thread for connection manager.
 366:  */
 367: public void run() {
 368:     for (;serverThread != null;) { // if serverThread==null, then exit thread
 369:         try {
 370: //System.out.println("Waiting for connection on " + serverPort);
 371:             UnicastConnection conn = getServerConnection();
 372: 
 373:             // get address of remote host for the RMIIncomingThread object
 374:             String remoteHost = null;
 375:             if (conn.sock != null) {
 376:                 remoteHost = conn.sock.getInetAddress().getHostAddress();            
 377:             }
 378: 
 379:             // use a thread pool to improve performance
 380:             //ConnectionRunnerPool.dispatchConnection(conn);
 381:             (new RMIIncomingThread(conn, remoteHost)).start();
 382: //       (new Thread(conn)).start();
 383:         }
 384:         catch (Exception e) {
 385:             e.printStackTrace();
 386:         }
 387:     }
 388: }
 389: 
 390: /**
 391:  * Serialization routine.
 392:  */
 393: void write(ObjectOutput out) throws IOException {
 394:         out.writeUTF(serverName);
 395:         out.writeInt(serverPort);
 396: }
 397: 
 398: /**
 399:  * Serialization routine.
 400:  */
 401: static UnicastConnectionManager read(ObjectInput in) throws IOException {
 402:         String host = in.readUTF();
 403:         int port = in.readInt();
 404:         //RMIClientSocketFactory csf = ((RMIObjectInputStream)in).manager.clientFactory;
 405:         //return (getInstance(host, port, csf));
 406:         return (getInstance(host, port, null));
 407: }
 408: 
 409: }
 410: 
 411: /**
 412:  * This is use as the hashkey for the client/server connections.
 413:  */
 414: class TripleKey {
 415: 
 416: String host;
 417: int port;
 418: Object other;
 419: 
 420: TripleKey(String host, int port, Object other) {
 421:     this.host = host;
 422:     this.port = port;
 423:     this.other = other;
 424: }
 425: 
 426: /**
 427:  * Hash code just include the host and other - we ignore the port since
 428:  * this has unusual matching behaviour.
 429:  */
 430: public int hashCode() {
 431:     return (host.hashCode() ^ other.hashCode());
 432: }
 433: 
 434: public boolean equals(Object obj) {
 435:     if (obj instanceof TripleKey) {
 436:         TripleKey other = (TripleKey)obj;
 437:         if (this.host.equals(other.host) &&
 438:             this.other == other.other &&
 439:             (this.port == other.port /* || this.port == 0 || other.port == 0*/)) {
 440:             return (true);
 441:         }
 442:     }
 443:     return (false);
 444: }
 445: 
 446: }