/** * a software bus package * * @author François-Régis Colin * @author Yannick Jestin * @author http://www.tls.cena.fr/products/ivy/ * */ package fr.dgac.ivy ; import java.net.*; import java.io.*; import java.util.*; /** * A class connecting to the Ivy software bus. * For example: *
 *Ivy bus = new Ivy("Dummy agent","ready",null);
 *bus.bindMsg("(.*)",myMessageListener);
 *bus.start(null);
 *
* * CHANGELOG: * 1.2.1: * - bus.start(null) now starts on DEFAULT_DOMAIN * - added the getDomains in order to correctly display the domain list * - checks if the serverThread exists before interrupting it * - no has unBindMsg(String) * 1.2.0: * - setSoTimeout is back on the server socket * - added a regression test main() * - clients is now a Hashtable. the deletion now works better * - getIvyClientsByName allows the research of IvyClient by name * - getDomain doesnt throw IvyException anymore * - removed the close() disconnect(IvyClient c). Fixes a big badaboum bug * - getDomain becomes public * - adding the sendToSelf feature * - fixed the printStackTrace upon closing of the ServerSocket after a close() */ public class Ivy implements Runnable { /** * the name of the application on the bus */ String appName; /** * the protocol version number */ public static final int PROCOCOLVERSION = 3 ; /** * the port for the UDP rendez vous, if none is supplied */ public static final int DEFAULT_PORT = 2010 ; /** * the domain for the UDP rendez vous */ public static final String DEFAULT_DOMAIN = "127.255.255.255:"+DEFAULT_PORT; /** * the library version, useful for development purposes only, when java is * invoked with -DIVY_DEBUG */ public static final String libVersion ="1.2.2"; private boolean debug; private static int serial=0; /* an unique ID for each regexp */ private static int clientSerial=0; /* an unique ID for each IvyClient */ private ServerSocket app; private Vector watchers; private volatile Thread serverThread; // to ensure quick communication of the end private Hashtable callbacks = new Hashtable(); private Hashtable clients = new Hashtable(); private Vector ivyApplicationListenerList = new Vector(); private String messages_classes[] = null; private boolean sendToSelf = false ; private boolean stopped = false; int applicationPort; /* Application port number */ Hashtable regexp_out = new Hashtable(); String ready_message = null; public final static int TIMEOUTLENGTH = 3000; /** * Readies the structures for the software bus connexion. * * All the dirty work is done un the start() method * @see #start * @param name The name of your Ivy agent on the software bus * @param message The hellow message you will send once ready * @param appcb A callback handling the notification of connexions and * disconnections, may be null */ public Ivy( String name, String message, IvyApplicationListener appcb) { appName = name; ready_message = message; debug = (System.getProperty("IVY_DEBUG")!=null); if ( appcb != null ) ivyApplicationListenerList.addElement( appcb ); } /** * connects the Ivy bus to a domain or list of domains. * *
  • One thread (IvyWatcher) for each traffic rendezvous (either UDP broadcast or TCPMulticast) *
  • One thread (serverThread/Ivy) to accept incoming connexions on server socket *
  • a thread for each IvyClient when the connexion has been done * * @param domainbus a domain of the form 10.0.0:1234, it is similar to the * netmask without the trailing .255. This will determine the meeting point * of the different applications. Right now, this is done with an UDP * broadcast. Beware of routing problems ! You can also use a comma * separated list of domains. * */ public void start(String domainbus) throws IvyException { if (domainbus==null) domainbus=DEFAULT_DOMAIN; try { app = new ServerSocket(0); app.setSoTimeout(TIMEOUTLENGTH); applicationPort = app.getLocalPort(); } catch (IOException e) { throw new IvyException("can't open TCP service socket " + e ); } traceDebug("lib: "+libVersion+" protocol: "+PROCOCOLVERSION+" TCP service open on port "+applicationPort); watchers = new Vector(); Domain[] d = parseDomains(domainbus); // readies the rendezvous : an IvyWatcher (thread) per domain bus for (int index=0;indexThere is one thread for each client connected, we could also * create another thread each time we send a message. * @param message A String which will be compared to the regular * expressions of the different clients * @return the number of messages actually sent */ public int sendMsg( String message ) { int count = 0; // an alternate implementation would one sender thread per client // instead of one for all the clients. It might be a performance issue for ( Enumeration e=clients.elements();e.hasMoreElements();) { IvyClient client = (IvyClient)e.nextElement(); count += client.sendMsg( message ); } if (sendToSelf) { // TODO } return count; } /** * Subscribes to a regular expression. * * The callback will be executed with * the saved parameters of the regexp as arguments when a message will sent * by another agent. A program doesn't receive its own messages. *

    Example: *
    the Ivy agent A performs

    b.bindMsg("^Hello (*)",cb);
    *
    the Ivy agent B performs
    b2.sendMsg("Hello world");
    *
    a thread in A will uun the callback cb with its second argument set * to a array of String, with one single element, "world" * @param regexp a perl regular expression, groups are done with parenthesis * @param callback any objects implementing the IvyMessageListener * interface, on the AWT/Swing framework * @return the id of the regular expression */ public int bindMsg(String regexp, IvyMessageListener callback ) { // creates a new binding (regexp,callback) Integer key = new Integer(serial++); regexp_out.put(key,regexp); callbacks.put(key,callback ); // notifies the other clients this new regexp for (Enumeration e=clients.elements();e.hasMoreElements();) { IvyClient c = (IvyClient)e.nextElement(); c.sendRegexp(key.intValue(),regexp); } return key.intValue(); } /** * unsubscribes a regular expression * * @param id the id of the regular expression, returned when it was bound */ public void unBindMsg(int id) throws IvyException { Integer key = new Integer(id); if ( ( regexp_out.remove(key) == null ) || (callbacks.remove(key) == null ) ) { throw new IvyException("client wants to remove an unexistant regexp "+id); } for (Enumeration e=clients.elements();e.hasMoreElements();) { ((IvyClient)e.nextElement()).delRegexp(id ); } } /** * unsubscribes a regular expression * * @return a boolean, true if the regexp existed, false otherwise or * whenever an exception occured during unbinding * @param String the string for the regular expression */ public boolean unBindMsg(String re) { for (Enumeration e=regexp_out.keys();e.hasMoreElements();) { Integer k = (Integer)e.nextElement(); if ( ((String)regexp_out.get(k)).compareTo(re) == 0) { try { unBindMsg(k.intValue()); } catch (IvyException ie) { return false; } return true; } } return false; } /** * adds an application listener to a bus * @param callback is an object implementing the IvyApplicationListener * interface * @return the id of the application listener, useful if you wish to remove * it later */ public int addApplicationListener(IvyApplicationListener callback){ ivyApplicationListenerList.addElement(callback); int id = ivyApplicationListenerList.indexOf( callback ); return id; } /** * removes an application listener * @param id the id of the application listener to remove */ public void removeApplicationListener(int id){ ivyApplicationListenerList.removeElementAt(id); } /* invokes the application listeners upon arrival of a new Ivy client */ public void connect(IvyClient client){ for ( int i = 0 ; i < ivyApplicationListenerList.size(); i++ ) { ((IvyApplicationListener)ivyApplicationListenerList.elementAt(i)).connect(client); } } /* invokes the application listeners upon arrival of a new Ivy client */ void disconnectReceived(IvyClient client){ for ( int i = 0 ; i < ivyApplicationListenerList.size(); i++ ) { ((IvyApplicationListener)ivyApplicationListenerList.elementAt(i)).disconnect(client); } } /* * removes a client from the list */ void removeClient(IvyClient c) { clients.remove(c.getClientKey()); } /** * invokes the application listeners when we are summoned to die * then stops */ public void dieReceived(IvyClient client, int id){ for ( int i=0 ;iivy<-- "+s); } /* a small private method for debbugging purposes */ private String getClientNames() { String s = appName+" clients are: "; for (Enumeration e=clients.elements();e.hasMoreElements();){ s+=((IvyClient)e.nextElement()).getApplicationName()+" "; } return s; } class Domain { private String domainaddr; private int port; public Domain(String domainaddr,int port) {this.domainaddr=domainaddr;this.port=port;} public String toString() {return domainaddr+":"+port;} public String getDomainaddr() { return domainaddr; } public int getPort() { return port; } } /* * unitary test. Normally, running java fr.dgac.ivy.Ivy should stop in 2.3 seconds :) */ public static void main(String[] args) { Ivy bus = new Ivy("Test Unitaire","TU ready",null); try { bus.start(null); try { Thread.sleep(2000); } catch (InterruptedException ie) { } bus.stop(); } catch (IvyException ie) { ie.printStackTrace(); } } } // class Ivy /* EOF */