| /* UnicastConnectionManager.java -- |
| Copyright (c) 1996, 1997, 1998, 1999, 2002, 2004 |
| Free Software Foundation, Inc. |
| |
| This file is part of GNU Classpath. |
| |
| GNU Classpath is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License as published by |
| the Free Software Foundation; either version 2, or (at your option) |
| any later version. |
| |
| GNU Classpath 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 for more details. |
| |
| You should have received a copy of the GNU General Public License |
| along with GNU Classpath; see the file COPYING. If not, write to the |
| Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
| 02110-1301 USA. |
| |
| Linking this library statically or dynamically with other modules is |
| making a combined work based on this library. Thus, the terms and |
| conditions of the GNU General Public License cover the whole |
| combination. |
| |
| As a special exception, the copyright holders of this library give you |
| permission to link this library with independent modules to produce an |
| executable, regardless of the license terms of these independent |
| modules, and to copy and distribute the resulting executable under |
| terms of your choice, provided that you also meet, for each linked |
| independent module, the terms and conditions of the license of that |
| module. An independent module is a module which is not derived from |
| or based on this library. If you modify this library, you may extend |
| this exception to your version of the library, but you are not |
| obligated to do so. If you do not wish to do so, delete this |
| exception statement from your version. */ |
| |
| |
| package gnu.java.rmi.server; |
| |
| 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.net.UnknownHostException; |
| import java.rmi.RemoteException; |
| import java.rmi.server.RMIClientSocketFactory; |
| import java.rmi.server.RMIServerSocketFactory; |
| import java.rmi.server.RMISocketFactory; |
| import java.util.ArrayList; |
| import java.util.ConcurrentModificationException; |
| import java.util.Hashtable; |
| import java.util.Iterator; |
| |
| public class UnicastConnectionManager |
| implements Runnable, ProtocolConstants { |
| |
| private static String localhost; |
| // use different maps for server/client type UnicastConnectionManager |
| private static Hashtable servers = new Hashtable(); |
| // Package-private to avoid trampolines. |
| static Hashtable clients = new Hashtable(); |
| ArrayList connections; //client connection pool |
| |
| // make serverThread volatile for poll |
| private volatile Thread serverThread; |
| private ServerSocket ssock; |
| String serverName; |
| int serverPort; |
| |
| // Package-private to avoid a trampoline. |
| static Thread scavenger; |
| |
| // If client and server are in the same VM, serverobj represents server |
| Object serverobj; |
| |
| private static RMISocketFactory defaultSocketFactory = RMISocketFactory.getSocketFactory(); |
| private RMIServerSocketFactory serverFactory; |
| private RMIClientSocketFactory clientFactory; |
| |
| // The following is for debug |
| private static int ncsock = 0; //count of client socket |
| private static int nssock = 0; //count of server socket |
| private static int ncmanager = 0; //count of client manager |
| private static int nsmanager = 0; //count of server manager |
| |
| private static final boolean debug = false; |
| |
| private static final Object GLOBAL_LOCK = new Object(); |
| |
| static { |
| try { |
| //Use host address instead of host name to avoid name resolving issues |
| //localhost = InetAddress.getLocalHost().getHostName(); |
| localhost = InetAddress.getLocalHost().getHostAddress(); |
| } |
| catch (UnknownHostException _) { |
| localhost = "localhost"; |
| } |
| |
| |
| } |
| |
| //Only one scavenger thread running globally |
| private static void startScavenger(){ |
| scavenger = new Thread(new Runnable(){ |
| public void run(){ |
| if (debug) System.out.println("************* start scavenger."); |
| boolean liveon = true; |
| while (liveon){ |
| // Sleep for the expire timeout |
| try{ |
| Thread.sleep(UnicastConnection.CONNECTION_TIMEOUT); |
| }catch(InterruptedException _ie){ |
| break; |
| } |
| liveon = false; |
| // Scavenge all clients' connections that're expired |
| Iterator iter = clients.values().iterator(); |
| long l = System.currentTimeMillis(); |
| try{ |
| while(iter.hasNext()){ |
| UnicastConnectionManager man = (UnicastConnectionManager)iter.next(); |
| ArrayList conns = man.connections; |
| synchronized(conns) { // is the lock a little coarser? |
| for (int last = conns.size() - 1; |
| last >= 0; |
| --last) |
| { |
| UnicastConnection conn = (UnicastConnection)conns.get(last); |
| if (UnicastConnection.isExpired(conn, l)){ |
| conns.remove(last); |
| conn.disconnect(); |
| conn = null; |
| }else |
| liveon = true; //there're still live connections |
| } |
| } |
| } |
| }catch(ConcurrentModificationException cme) { |
| // handle it lazily |
| liveon = true; |
| } |
| } |
| scavenger = null; |
| if (debug) System.out.println("************* exit scavenger."); |
| } |
| }); |
| // As it is used for client connection, we may put this thread |
| // in daemon state to prevent the VM from blocking when exiting. |
| scavenger.setDaemon(true); |
| scavenger.start(); |
| } |
| |
| /** |
| * Client UnicastConnectionManager constructor |
| */ |
| private UnicastConnectionManager(String host, int port, RMIClientSocketFactory csf) { |
| ssock = null; |
| serverName = host; |
| serverPort = port; |
| serverFactory = null; |
| clientFactory = csf; |
| connections = new ArrayList(); |
| } |
| |
| /** |
| * Server UnicastConnectionManager constructor |
| */ |
| private UnicastConnectionManager(int port, RMIServerSocketFactory ssf) throws RemoteException { |
| |
| try { |
| ssock = ssf.createServerSocket(port); |
| serverPort = ssock.getLocalPort(); |
| } |
| catch (IOException ioex) { |
| ssock = null; |
| serverPort = 0; |
| throw new java.rmi.server.ExportException("can not create Server Socket on port " + port,ioex); |
| } |
| serverName = localhost; |
| serverFactory = ssf; |
| clientFactory = null; |
| } |
| |
| /** |
| * Return a client connection manager which will connect to the given |
| * host/port. |
| */ |
| public static synchronized UnicastConnectionManager getInstance(String host, int port, RMIClientSocketFactory csf) { |
| //System.out.println("getInstance: " + host + "," + port + "," + csf); |
| if (csf == null) { |
| csf = defaultSocketFactory; |
| } |
| // change host name to host address to avoid name resolving issues |
| try{ |
| host = InetAddress.getByName(host).getHostAddress(); |
| }catch(Exception _){} |
| |
| TripleKey key = new TripleKey(host, port, csf); |
| UnicastConnectionManager man = (UnicastConnectionManager)clients.get(key); |
| if (man == null) { |
| man = new UnicastConnectionManager(host, port, csf); |
| if (debug) { |
| ncmanager++; |
| System.out.println("\n\n ====== " + ncmanager + " client managers.\n\n"); |
| } |
| clients.put(key, man); |
| |
| // Detect if client and server are in the same VM, i.e., their keys are equal |
| UnicastConnectionManager svrman = (UnicastConnectionManager)servers.get(key); |
| if(svrman != null){ // server and client are in the same VM |
| man.serverobj = svrman.serverobj; |
| } |
| } |
| return (man); |
| } |
| |
| /** |
| * Return a server connection manager which will accept connection on the |
| * given port. |
| */ |
| public static synchronized UnicastConnectionManager getInstance(int port, RMIServerSocketFactory ssf) throws RemoteException { |
| //System.out.println("getInstance: " + port + "," + ssf); |
| if (ssf == null) { |
| ssf = defaultSocketFactory; |
| } |
| TripleKey key = new TripleKey(localhost, port, ssf); |
| UnicastConnectionManager man = (UnicastConnectionManager)servers.get(key); |
| if (man == null) { |
| man = new UnicastConnectionManager(port, ssf); |
| if (debug) { |
| nsmanager++; |
| System.out.println("\n\n ****** " + nsmanager + " server managers.\n\n"); |
| } |
| // The provided port might not be the set port. |
| key.port = man.serverPort; |
| servers.put(key, man); |
| } |
| return (man); |
| } |
| |
| /** |
| * Get a connection from this manager. |
| */ |
| public UnicastConnection getConnection() throws IOException { |
| if (ssock == null) { |
| return (getClientConnection()); |
| } |
| else { |
| return (getServerConnection()); |
| } |
| } |
| |
| /** |
| * Accept a connection to this server. |
| */ |
| private UnicastConnection getServerConnection() throws IOException { |
| Socket sock = ssock.accept(); |
| sock.setTcpNoDelay(true); //?? |
| UnicastConnection conn = new UnicastConnection(this, sock); |
| conn.acceptConnection(); |
| if (debug){ |
| nssock++; |
| System.out.println("\n\n ****** " + nssock + " server socks.\n\n"); |
| } |
| //System.out.println("Server connection " + sock); |
| return (conn); |
| } |
| |
| /** |
| * Make a conection from this client to the server. |
| */ |
| private UnicastConnection getClientConnection() throws IOException { |
| ArrayList conns = connections; |
| UnicastConnection conn; |
| |
| synchronized(conns) { |
| int nconn = conns.size() - 1; |
| |
| // if there're free connections in connection pool |
| if(nconn >= 0) { |
| conn = (UnicastConnection)conns.get(nconn); |
| //Should we check if conn is alive using Ping?? |
| conns.remove(nconn); |
| |
| // Check if the connection is already expired |
| long l = System.currentTimeMillis(); |
| if (!UnicastConnection.isExpired(conn, l)){ |
| return conn; |
| }else { |
| conn.disconnect(); |
| conn = null; |
| } |
| } |
| } |
| |
| Socket sock = clientFactory.createSocket(serverName, serverPort); |
| conn = new UnicastConnection(this, sock); |
| conn.makeConnection(DEFAULT_PROTOCOL); |
| |
| if (debug) { |
| ncsock++; |
| System.out.println("\n\n ====== " + ncsock + " client socks.\n\n"); |
| } |
| |
| return (conn); |
| } |
| |
| /** |
| * Get the string representation, describing the connection. |
| */ |
| public String toString() |
| { |
| return serverName+":"+serverPort+" ("+serverobj+")"; |
| } |
| |
| /** |
| * Discard a connection when we're done with it - maybe it can be |
| * recycled. |
| */ |
| public void discardConnection(UnicastConnection conn) { |
| //System.out.println("Discarding connection " + conn); |
| //conn.disconnect(); |
| if (ssock != null) //server connection |
| conn.disconnect(); |
| else { |
| // To client connection, we'd like to return back to pool |
| UnicastConnection.resetTime(conn); |
| //Ensure there're only one scavenger globally |
| synchronized(GLOBAL_LOCK) { |
| connections.add(conn); //borrow this lock to garantee thread safety |
| if (scavenger == null) |
| startScavenger(); |
| } |
| } |
| } |
| |
| /** |
| * Start a server on this manager if it's a server socket and we've not |
| * already got one running. |
| */ |
| public void startServer() { |
| synchronized(this) { |
| if (ssock == null || serverThread != null) { |
| return; |
| } |
| serverThread = new Thread(this); |
| // The following is not necessary when java.lang.Thread's constructor do this. |
| // serverThread.setContextClassLoader(Thread.currentThread().getContextClassLoader()); |
| } |
| serverThread.start(); |
| } |
| |
| /** |
| * Stop a server on this manager |
| */ |
| public void stopServer() { |
| synchronized(this) { |
| if(serverThread != null){ |
| serverThread = null; |
| try{ |
| ssock.close(); |
| }catch(Exception _){} |
| } |
| } |
| } |
| |
| /** |
| * Server thread for connection manager. |
| */ |
| public void run() { |
| for (;serverThread != null;) { // if serverThread==null, then exit thread |
| try { |
| //System.out.println("Waiting for connection on " + serverPort); |
| UnicastConnection conn = getServerConnection(); |
| |
| // get address of remote host for the RMIIncomingThread object |
| String remoteHost = null; |
| if (conn.sock != null) { |
| remoteHost = conn.sock.getInetAddress().getHostAddress(); |
| } |
| |
| // use a thread pool to improve performance |
| //ConnectionRunnerPool.dispatchConnection(conn); |
| (new RMIIncomingThread(conn, remoteHost)).start(); |
| // (new Thread(conn)).start(); |
| } |
| catch (Exception e) { |
| e.printStackTrace(); |
| } |
| } |
| } |
| |
| /** |
| * Serialization routine. |
| */ |
| void write(ObjectOutput out) throws IOException { |
| out.writeUTF(serverName); |
| out.writeInt(serverPort); |
| } |
| |
| /** |
| * Serialization routine. |
| */ |
| static UnicastConnectionManager read(ObjectInput in) throws IOException { |
| String host = in.readUTF(); |
| int port = in.readInt(); |
| //RMIClientSocketFactory csf = ((RMIObjectInputStream)in).manager.clientFactory; |
| //return (getInstance(host, port, csf)); |
| return (getInstance(host, port, null)); |
| } |
| |
| } |
| |
| /** |
| * This is use as the hashkey for the client/server connections. |
| */ |
| class TripleKey { |
| |
| String host; |
| int port; |
| Object other; |
| |
| TripleKey(String host, int port, Object other) { |
| this.host = host; |
| this.port = port; |
| this.other = other; |
| } |
| |
| /** |
| * Hash code just include the host and other - we ignore the port since |
| * this has unusual matching behaviour. |
| */ |
| public int hashCode() { |
| return (host.hashCode() ^ other.hashCode()); |
| } |
| |
| public boolean equals(Object obj) { |
| if (obj instanceof TripleKey) { |
| TripleKey other = (TripleKey)obj; |
| if (this.host.equals(other.host) && |
| this.other == other.other && |
| (this.port == other.port /* || this.port == 0 || other.port == 0*/)) { |
| return (true); |
| } |
| } |
| return (false); |
| } |
| |
| /** |
| * Get the string representation, describing the connection. |
| */ |
| public String toString() |
| { |
| return host+":"+port+" ("+other+")"; |
| } |
| |
| } |