From a5ca9fd3f54783b7f9371d69574660f29fe0d10f Mon Sep 17 00:00:00 2001 From: jestin Date: Fri, 11 Aug 2000 09:15:47 +0000 Subject: Probe is the java implementation of ivyprobe(1). It supports the same syntax. --- src/Ivy.java | 362 +++++++++++++++++++++++------------- src/IvyApplicationAdapter.java | 24 ++- src/IvyApplicationListener.java | 41 ++++- src/IvyClient.java | 396 ++++++++++++++++++++-------------------- src/IvyException.java | 11 +- src/IvyMessageListener.java | 14 ++ src/IvyWatcher.java | 278 ++++++++++++++-------------- src/Probe.java | 77 ++++++++ src/TestIvy.java | 115 ++++++------ src/TestIvySwing.java | 120 +++++++----- 10 files changed, 860 insertions(+), 578 deletions(-) create mode 100644 src/Probe.java diff --git a/src/Ivy.java b/src/Ivy.java index 8756c94..5b423eb 100755 --- a/src/Ivy.java +++ b/src/Ivy.java @@ -1,8 +1,11 @@ -// -// API de connexion au bus logiciel ivy -// -// - +/** + * 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.*; @@ -11,179 +14,270 @@ import java.util.Vector; import java.util.Hashtable; import java.util.StringTokenizer; +/** + * A class connecting to the Ivy software bus. + * For example: + *
+ *Ivy bus = new Ivy("Dummy agent","ready",null);
+ *bus.bindMsg("(.*)",myMessageListener);
+ *bus.start(null);
+ *
+ */ public class Ivy implements Runnable, IvyApplicationListener { - static private boolean debug = (System.getProperty("IVY_DEBUG")!=null) ; - + /** + * the name of the application on the bus + */ + public 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; - public String appName; - private boolean ivyRunning = false; - private String ready_message = null; + + private static boolean debug = (System.getProperty("IVY_DEBUG")!=null) ; + private static int serial=0; /* an unique ID for each regexps */ private ServerSocket app; private IvyWatcher watch; private Thread server; - - private Hashtable regexp_out = new Hashtable(); private Hashtable callbacks = new Hashtable(); private Vector clients = new Vector(); private Vector ivyApplicationListenerList = new Vector(); private String messages_classes[] = null; - private int myport; /* Application port number */ - - public synchronized boolean ivyRunning(){ return ivyRunning; } + int applicationPort; /* Application port number */ + boolean ivyRunning = false; + Hashtable regexp_out = new Hashtable(); + String ready_message = null; + + /** + * 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; if ( appcb != null ) ivyApplicationListenerList.addElement( appcb ); } - void classes( String msg_classes[] ) { messages_classes = msg_classes; } - - public Hashtable getRegexpOut() { return regexp_out; } - - public String getReadyMessage() { return ready_message; } - - void addClient( Socket socket ) throws IOException { - IvyClient client = new IvyClient( this, socket); - clients.addElement( client ); - } - - void removeClient( IvyClient client ) { - clients.removeElement( client ); + /** + * connects the Ivy bus to a domain or list of domains + * @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 { + try { + app = new ServerSocket(0); + applicationPort = app.getLocalPort(); + } catch (IOException e) { + throw new IvyException("can't open TCP service socket " + e ); + } + traceDebug("TCP service open on port "+applicationPort); + watch = new IvyWatcher(this); + ivyRunning = true; + server = new Thread(this); + server.start(); + watch.start(getDomain(domainbus)); } - void callCallback(IvyClient client, Integer key, String msgarg) { - IvyMessageListener callback=(IvyMessageListener)callbacks.get(key); - if (callback==null){ - traceDebug("(callCallback) Not regexp matching id "+key.intValue()); - return; + /** + * disconnects from the Ivy bus. + */ + public void stop() { + try { + ivyRunning = false; + watch.stop(); + app.close(); + for ( int i = 0 ; i < clients.size(); i++ ) { + IvyClient client = (IvyClient)clients.elementAt(i); + client.close("normal Ivy Stopping..."); + // advertise that this is a normal + //close for debugging purposes + } + } catch (IOException e) { + traceDebug("IOexception Stop "); } - StringTokenizer st = new StringTokenizer(msgarg,IvyClient.EndArg); - String args[] = new String[st.countTokens()]; - int i=0; - while (st.hasMoreTokens()){ args[i++] = st.nextToken();} - callback.receive( client, args ); + clients.removeAllElements(); } - public int getApplicationPort() { return myport; } - + /** + * Performs a pattern matching according to everyone's regexps, and sends + * the results to the relevant ivy agents. + *

There 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; - // envoie le message à tous les clients du bus - // TODO: il faudrait mettre une Thread emission par client */ + int count = 0; + // TODO: il faudrait mettre une Thread emission par client */ for ( int i = 0 ; i < clients.size(); i++ ) { IvyClient client = (IvyClient)clients.elementAt(i); count += client.sendMsg( message ); } - return count; + return count; } - static private int serial=0; + /** + * 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 ) { - // associe une nouvelle regexp à un nouveau callback + // creates a new binding (regexp,callback) Integer key = new Integer(serial++); regexp_out.put(key,regexp); callbacks.put(key,callback ); - // indique aux autres clients la nouvelle regexp + // notifies the other clients this new regexp for (int i=0;iivy<-- "+s); } - - + // TODO find out if this is useful or not ... + private void classes( String msg_classes[] ) { + messages_classes = msg_classes; + } -} // class Ivy +} diff --git a/src/IvyApplicationAdapter.java b/src/IvyApplicationAdapter.java index 16491b5..af198dd 100755 --- a/src/IvyApplicationAdapter.java +++ b/src/IvyApplicationAdapter.java @@ -1,14 +1,20 @@ -/* -** -** BusCallbackAdapter -** -** la même pratique que pour l awt 1.1. Quand on ne veut implémenter qu une -** des trois fonctions membres, il suffit d étendre l'adapter -** pas de constructeur ... c'est donc une class abstraite -*/ - package fr.dgac.ivy; + +/** + * this class is a dummy ApplicationListener + * + * @author François-Régis Colin + * @author Yannick Jestin + * @author http://www.tls.cena.fr/products/ivy/ + * + * an ApplicatinListener class for handling application-level request on the + * Ivy bus. The methods in this class are empty. This class exists as a + * convenience for implementing a subset of the methods of the + * applicationlistener. See the AWT 1.1 framework for further information on + * this. + */ + public abstract class IvyApplicationAdapter implements IvyApplicationListener { public void connect( IvyClient client ) { } public void disconnect( IvyClient client ) { } diff --git a/src/IvyApplicationListener.java b/src/IvyApplicationListener.java index 731f8a7..bdfb12c 100755 --- a/src/IvyApplicationListener.java +++ b/src/IvyApplicationListener.java @@ -1,9 +1,38 @@ package fr.dgac.ivy; -public interface IvyApplicationListener extends java.util.EventListener -{ - public abstract void connect(IvyClient client); - public abstract void disconnect(IvyClient client); - public abstract void die(IvyClient client, int id); - public abstract void directMessage( IvyClient client, int id,String msgarg ); +/** + * this interface specifies the methods of an ApplicationListener + * + * @author François-Régis Colin + * @author Yannick Jestin + * @author http://www.tls.cena.fr/products/ivy/ + * + * The ApplicatinListenr for receiving application level events on the Ivy + * bus: connexion, disconnexion, direct messages or requests to quit. + */ + +public interface IvyApplicationListener extends java.util.EventListener { + /** + * invoked when a Ivy Client has joined the bus + * @param client the peer + */ + public abstract void connect(IvyClient client); + /** + * invoked when a Ivy Client has left the bus + * @param client the peer + */ + public abstract void disconnect(IvyClient client); + /** + * invoked when a peer request us to leave the bus + * @param client the peer + */ + public abstract void die(IvyClient client, int id); + /** + * invoked when a peer sends us a direct message + * @param client the peer + * @param id + * @param msgarg the message itself + * this is not yet implemented in java. I believe it has no real use :) + */ + public abstract void directMessage( IvyClient client, int id,String msgarg ); } diff --git a/src/IvyClient.java b/src/IvyClient.java index 7ec220f..dd0f156 100755 --- a/src/IvyClient.java +++ b/src/IvyClient.java @@ -1,7 +1,3 @@ -/* -** IvyClient -*/ - package fr.dgac.ivy ; import java.lang.Thread; @@ -10,121 +6,93 @@ import java.io.*; import java.util.*; import gnu.regexp.*; -public class IvyClient extends Thread { +/** + * A private Class for the the peers on the bus + * + * @author François-Régis Colin + * @author Yannick Jestin + * @author http://www.tls.cena.fr/products/ivy/ + * + * each time a connexion is made with a remote peer, the regexp are exchanged + * once ready, a ready message is sent, and then we can send messages, + * die messages, direct messages, add or remove regexps, or quit. A thread is + * created for each remote client. + */ + +class IvyClient extends Thread { - /* - * les types de messages - * on ne *change* pas, sous peine d'incompatibilité avec les autres - * implémentations ( C, C++, etc... ). - * quoi que :) - * - */ - public final static int Bye = 0; /* l'application emettrice se termine */ - public final static int AddRegexp = 1;/* expression reguliere d'un client */ - public final static int Msg = 2 ; /* message reel */ - public final static int Error = 3; /* error message */ - public final static int DelRegexp = 4;/* Remove expression reguliere */ - public final static int EndRegexp = 5;/* fin de liste regexp */ - public final static int StartRegexp = 6;/* debut des expressions */ - public final static int DirectMsg = 7;/* message direct a l'appli */ - public final static int Die = 8; /* demande de terminaison de l'appli */ - public final static int Ping = 9; /* demande de réponse pong */ - public final static int Pong = 10; /* réponse au ping */ - public final static String StartArg = "\002";/* separateur debut args */ - public final static String EndArg = "\003"; /* separateur inter arguments */ - - private String appName; - private int appPort; + /* the protocol magic numbers */ + final static int Bye = 0; /* end of the peer */ + final static int AddRegexp = 1;/* the peer adds a regexp */ + final static int Msg = 2 ; /* the peer sends a message */ + final static int Error = 3; /* error message */ + final static int DelRegexp = 4;/* the peer removes one of his regex */ + final static int EndRegexp = 5;/* no more regexp in the handshake */ + final static int SchizoToken = 6; /* avoid race condition in concurrent connexions */ + final static int DirectMsg = 7;/* the peer sends a direct message */ + final static int Die = 8; /* the peer wants us to quit */ + final static String StartArg = "\002";/* begin of arguments */ + final static String EndArg = "\003"; /* end of arguments */ - /* - * gestion du protocole ping - * ne marche pas encore - */ - private long lastRead = 0; - private long pingDate = 0 ; - private long pingLag =0 ; - + private static boolean debug = (System.getProperty("IVY_DEBUG")!=null) ; private Ivy bus; private Socket socket; private BufferedReader in; private OutputStream out; private Hashtable regexp_in = new Hashtable(); + private Hashtable regexp_text = new Hashtable(); + private String appName; + private int appPort; + private boolean gardefou=true; + private boolean peerCalling; - static private boolean debug = (System.getProperty("IVY_DEBUG")!=null) ; - - IvyClient( Ivy bus, Socket socket ) throws IOException { + IvyClient(Ivy bus, Socket socket,boolean peerCalling) throws IOException { appName = "Unkown"; appPort = 0; this.bus = bus; this.socket = socket; - lastRead = (new Date()).getTime(); + this.peerCalling=peerCalling; in = new BufferedReader(new InputStreamReader(socket.getInputStream())); out = socket.getOutputStream(); - sendService( bus.getRegexpOut()); - start(); - } - - /* - * quelques accesseurs - */ - public InetAddress getRemoteAddress() { return socket.getInetAddress(); } - public String getApplicationName() { return appName ; } - public int getAppPort() { return appPort ; } - - void sendBuffer( String buffer ) { - buffer += "\n"; - try { - out.write(buffer.getBytes() ); - out.flush(); - } catch ( IOException e ) { - // TODO: write error - System.err.println("IvyClient.sendBuffer.write failed. FIX ME"); + Hashtable regexps=bus.regexp_out; + // sends our ID, whether we initiated the connexion or not + // the ID is the couple "host name,application Port", the host name + // information is in the socket itself, the port is not known if we + // initiate the connexion + send(SchizoToken,bus.applicationPort,bus.appName); + // sends our regexps to the peer + for (Enumeration e = regexps.keys(); e.hasMoreElements(); ) { + Integer key = (Integer)e.nextElement(); + sendRegexp( key.intValue(),(String)regexps.get(key)); } + send( EndRegexp,0,""); + // spawns a thread to manage the incoming traffic on this + // socket. We should be ready to receive messages now. + start(); } - void send( int type, int id, String arg) { - String buffer = type+" "+id+StartArg+arg; - sendBuffer(buffer ); - } - - void sendRegexp( int id, String regexp ) { send( AddRegexp,id,regexp); } - public void sendDie( ) { send( Die,0,""); } + String getApplicationName() { return appName ; } - public void sendDie(String message) { send( Die,0,message); } - - void sendPing() { - pingDate = (new Date()).getTime(); - // on marque le message par la date actuelle - send( Ping, 0, String.valueOf(pingDate) ); - } - - /* - * notifie la liste des regexps aux autres clients du bus + /** + * allow an Ivy package class to access the list of regexps at a + * given time. + * perhaps we should implement a new IvyApplicationListener method to + * allow the notification of regexp addition and deletion */ - private void sendService( Hashtable regexps ) { - send( StartRegexp, bus.getApplicationPort(), bus.appName); - for (Enumeration e = regexps.keys(); e.hasMoreElements(); ) { - Integer key = (Integer)e.nextElement(); - sendRegexp( key.intValue(),(String)regexps.get(key)); - } - send( EndRegexp,0, ""); - } + Enumeration getRegexps() { return regexp_text.elements(); } + int getAppPort() { return appPort ; } - void send( int type, Integer id, int nbsub, REMatch result) { - String buffer = type+" "+id+StartArg; - traceDebug ( "Send matching result \n" ); - // ????Start at 1 because group 0 represent entire matching - for(int sub = 1; sub <= nbsub; sub++) { - if (result.getSubStartIndex(sub) > -1) { - buffer += result.toString(sub)+EndArg; - traceDebug( "Send arg "+result.toString(sub)); - } - } - sendBuffer( buffer ); - } + /* perhaps we should perform some checking here */ + void sendRegexp(int id,String regexp) {send(AddRegexp,id,regexp);} + public void delRegexp(int id) {send( DelRegexp,id,"");} - public int sendMsg( String message ) { + /** + * sends the substrings of a message to the peer for each matching regexp. + * @param message the string that will be match-tested + * @return the number of messages sent to the peer + */ + int sendMsg( String message ) { int count = 0; for (Enumeration e = regexp_in.keys();e.hasMoreElements();) { Integer key = (Integer)e.nextElement(); @@ -138,152 +106,178 @@ public class IvyClient extends Thread { return count; } + /** + * closes the connexion to the peer. + * @param msg the debug information + * the thread managing the socket is stopped + */ void close(String msg) throws IOException { traceDebug( msg ); - socket.close(); - //stop(); -} + socket.close(); // TODO it seems it doesnt stop the thread + out.close(); + in.close(); + gardefou=false; + } - public boolean sameClient( IvyClient clnt ) { + /** + * compares two peers the id is the couple (host,service port). + * @param clnt the other peer + * @return true if the peers are similir. This should not happen, it is bad + * © ® (tm) + */ + boolean sameClient( IvyClient clnt ) { return ( appPort != 0 && appPort == clnt.appPort ) && ( getRemoteAddress() == clnt.getRemoteAddress() ) ; } - public void delRegexp( int id ) { send( DelRegexp,id,"");} - + /** + * the code of the thread handling the incoming messages. + * this thread stops (at least it should) when the socket is closed + * or when gardefou=false + */ public void run() { String msg = null; try { - traceDebug("Connected from "+ - socket.getInetAddress().getHostName()+":"+socket.getPort()); - while((msg=in.readLine()) != null ) { - int msgtype; /* type du dernier message recu */ - Integer msgid; /* id du dernier message recu */ - RE regexp = null; /* regexp compile */ + traceDebug("Connected from "+ socket.getInetAddress().getHostName()+ + ":"+socket.getPort()); + while ( gardefou && ((msg=in.readLine()) != null )) { + int msgtype; // type of the last message received + Integer msgid; // ID of the last message received + RE regexp = null; // compiled regexp String token = null; - /* - * ETAPE UN : extraire le message + /* + * First stage: extract the message */ - lastRead = (new Date()).getTime(); StringTokenizer st = new StringTokenizer(msg); - if (!st.hasMoreTokens()){ - close("Bad format no type '"+msg+"'"); - break; - } + if(!st.hasMoreTokens()){close("Bad format no type '"+msg+"'");break;} token=st.nextToken(); - if (token.length()==0) { - close("Bad format no type '"+msg+"'"); - break; - } + if (token.length()==0){close("Bad format no type '"+msg+"'");break;} try { msgtype = Integer.parseInt(token); } catch ( NumberFormatException e ) { close("Bad format error parsing type'"+msg+"'"); break; } - if (!st.hasMoreTokens()) { - close("Bad format no id '"+msg+"'"); - break; - } - msgid=Integer.valueOf(st.nextToken(StartArg)); + if(!st.hasMoreTokens()){close("Bad format no id '"+msg+"'");break;} + token=st.nextToken(StartArg); /* - * DONE: il trainait un bug ici: on oubliait de réinitialiser msgarg - * à la chaine vide, et du coup, pour les regexps sans groupe (), - * ça envoyait des messages un peu fantaisistes + * TODO + * this doesn't work on jdk1.3 !!! + * the token equals " 3992", which provoques a NumberFormatException */ + try { + msgid=Integer.valueOf(token); + } catch ( NumberFormatException e ) { + close("Bad format error parsing id '"+token+"'"); + break; + } String msgarg=""; - if (st.hasMoreTokens()) msgarg=st.nextToken("\n"); - - /* - *ETAPE DEUX : traitement adapté au type de message + if (st.hasMoreTokens()) msgarg=st.nextToken("\n"); + /* + * second stage: process the message */ - switch (msgtype) { - case Bye: break; - case AddRegexp: + switch (msgtype) { + case Bye: break; + case AddRegexp: if ( bus.CheckRegexp(msgarg) ) { try { regexp_in.put(msgid,new RE(msgarg)); - } catch (REException e) { + regexp_text.put(msgid,msgarg); + } catch (REException e) { System.err.println("Bad pattern: "+e.getMessage()); } - } else { + } else { System.err.println( - "Warning exp='"+msgarg+"' can't match removing from "+appName); + "Warning exp='"+msgarg+"' can't match removing from "+appName); } - break; - case DelRegexp: + break; + case DelRegexp: regexp_in.remove(msgid); - break; - case EndRegexp: - /* - * call application callback avec event Connected - */ - bus.connect(this); - if (bus.getReadyMessage()!=null) sendMsg(bus.getReadyMessage()); - break; - case Msg: - /* - * call callback avec les arguments - */ - bus.callCallback(this,msgid,msgarg); - break; - case Error: - traceDebug("Error msg "+msgid+" "+msgarg); - break; - case StartRegexp: - appName=msgarg; - appPort=msgid.intValue(); - if ( bus.checkConnected(this) ) - close("Quitting Application already connected"); - break; - case DirectMsg: - traceDebug("Direct Message id="+msgid+" msg='"+msgarg+"'"); - /* - * call directCallback avec les arguments - */ - bus.directMessage( this, msgid.intValue(), msgarg ); - break; - case Die: - traceDebug("Die Message received . argh !"); - /* - * call diecallBack aavant de quitter + regexp_text.remove(msgid); + break; + case EndRegexp: + bus.connect(this); + /* TODO + * BUG ? the peer is perhaps not ready to handle this message + * an assymetric processing should be written */ - bus.die( this,msgid.intValue()); - break; - case Ping: - traceDebug("Ping Message"); - // répond avec le même argument .. Pour le moment c'est inutile - send(Pong,0,msgarg); + if (bus.ready_message!=null) sendMsg(bus.ready_message); break; - case Pong: - // calcul du lag en millisecondes - traceDebug("Pong Message"); - pingLag = (new Date()).getTime() - pingDate ; + case Msg: + try { + bus.callCallback(this,msgid,msgarg); + } catch (IvyException ie) { + // calling an inexistant callback + System.err.println("calling an inexistant callback, the caller must be wrong !"); + } break; - default: - System.err.println("*** IvyClient *** unhandled msg type "+ - msgtype+" "+msgid+msgarg); - break; - } // switch sur le type de message - } // while readline - - // plus rien à lire .... ou alors break donc erreur - traceDebug("zarbi Disconnected from "+ + case Error: + traceDebug("Error msg "+msgid+" "+msgarg); + break; + case SchizoToken: + appName=msgarg; + appPort=msgid.intValue(); + if ( bus.checkConnected(this) ) { + close("Quitting Application already connected"); + System.err.println("Rare ! A concurrent connect occured"); + } + break; + case DirectMsg: + bus.directMessage( this, msgid.intValue(), msgarg ); + break; + case Die: + bus.die( this,msgid.intValue()); + break; + default: + System.err.println("*** IvyClient *** unhandled msg type "+ + msgtype+" "+msgid+msgarg); + break; + } // switch + } // while gardefou + traceDebug("normally Disconnected from "+ + socket.getInetAddress().getHostName()+":"+socket.getPort()); + } catch (IOException e) { + traceDebug("abnormally Disconnected from "+ socket.getInetAddress().getHostName()+":"+socket.getPort()); - } catch ( IOException e ) { - traceDebug("ioexception Disconnected from "+ - socket.getInetAddress().getHostName()+":"+socket.getPort()); } - /* je meurs en tant que client du bus */ - /* mais avant, j'exécute mon application callback perso */ bus.disconnect( this ); bus.removeClient( this ); - } // run + } - public String From() { return socket.toString(); } + private void sendBuffer( String buffer ) { + buffer += "\n"; + try { + out.write(buffer.getBytes() ); + out.flush(); + } catch ( IOException e ) { + /* + * TODO + * we should throw an exception here + */ + System.err.println("IvyClient.sendBuffer.write failed. FIX ME"); + } + } + + private void send(int type, int id, String arg) { + sendBuffer(type+" "+id+StartArg+arg); + } + + private void send(int type, Integer id, int nbsub, REMatch result) { + String buffer = type+" "+id+StartArg; + // Start at 1 because group 0 represent entire matching + for(int sub = 1; sub <= nbsub; sub++) { + if (result.getSubStartIndex(sub) > -1) { + buffer += result.toString(sub)+EndArg; + } + } + sendBuffer(buffer); + } + private void sendDie() {send(Die,0,"");} + private void sendDie(String message) {send(Die,0,message);} + private InetAddress getRemoteAddress() { return socket.getInetAddress(); } private void traceDebug(String s){ - if (debug) System.out.println("-->ivyclient<-- "+s); + if (debug) System.out.println("-->IvyClient<-- "+s); } } // class IvyClient /* EOF */ diff --git a/src/IvyException.java b/src/IvyException.java index 263631b..e467771 100755 --- a/src/IvyException.java +++ b/src/IvyException.java @@ -1,5 +1,14 @@ package fr.dgac.ivy; +/** + * signals that an unrecoverrable Ivy exception has occured. + * + * @author François-Régis Colin + * @author Yannick Jestin + * @author http://www.tls.cena.fr/products/ivy/ + * + */ + public class IvyException extends Exception { - public IvyException(String s) { super(s); } + IvyException(String s) { super(s); } } diff --git a/src/IvyMessageListener.java b/src/IvyMessageListener.java index c0bfde4..e960afb 100755 --- a/src/IvyMessageListener.java +++ b/src/IvyMessageListener.java @@ -1,6 +1,20 @@ package fr.dgac.ivy; +/** + * this interface specifies the methods of an IvyMessageListener + * + * @author François-Régis Colin + * @author Yannick Jestin + * @author http://www.tls.cena.fr/products/ivy/ + * + */ + public interface IvyMessageListener extends java.util.EventListener { + /** + * this callback is invoked when a message has been received + * @param client the peer who sent the message + * @param args the array of string, on string for each subregexp + */ public abstract void receive(IvyClient client, String[] args); } diff --git a/src/IvyWatcher.java b/src/IvyWatcher.java index 21311e9..bae387a 100755 --- a/src/IvyWatcher.java +++ b/src/IvyWatcher.java @@ -1,8 +1,3 @@ -/* -* -* IvyWatcher : gestion des messages UDP pour annoncer les arrivées -* -*/ package fr.dgac.ivy ; import java.lang.Thread; @@ -11,156 +6,171 @@ import java.io.*; import java.util.StringTokenizer; import gnu.regexp.*; import java.util.Vector; -public class IvyWatcher extends Thread { - Ivy bus; /* master bus controler */ - DatagramSocket broadcast; /* supervision socket */ - private Vector domainaddrList; + +/** + * A private Class for the Ivy rendezvous + * + * @author François-Régis Colin + * @author Yannick Jestin + * @author http://www.tls.cena.fr/products/ivy/ + * + * right now, the rendez vous is on a UDP socket. The watcher will answer to + * each peer advertising its arrival on the bus. The intrinsics of Unix are so + * that the broadcast is done using the same socket, which is not a good + * thing. + */ + +class IvyWatcher implements Runnable { private static boolean debug = (System.getProperty("IVY_DEBUG")!=null); + private Vector domainaddrList; + private boolean watcherrunning = false; + private Thread broadcastListener ; + private Ivy bus; /* master bus controler */ + private DatagramSocket broadcast; /* supervision socket */ + + /** + * creates an Ivy watcher. + * @param bus the bus + */ + IvyWatcher(Ivy bus) throws IvyException { + this.bus = bus; + domainaddrList = new Vector(); + } - public void close() { + /** + * the behaviour of the thread watching the UDP socket. + * this thread will stop either when the bus stops or when the + * watcherrunning will be set to false + */ + public void run() { + traceDebug("IvyWatcher waiting for Broadcast"); + while( watcherrunning && bus.ivyRunning ) try { + byte buf[] = new byte[256]; + DatagramPacket packet=new DatagramPacket(buf, 256); + int port; + broadcast.receive(packet); + String msg = new String(packet.getData()) ; + InetAddress remotehost = packet.getAddress(); + traceDebug("BUSWATCHER Receive Broadcast from "+ + remotehost.getHostName()+":"+packet.getPort()); + // check if remoteaddr is in our broadcast domain list + // otherwise we ignore the broadcast + if ( !isInDomain( remotehost ) ) continue; + StringTokenizer st = new StringTokenizer(msg); + if ( !st.hasMoreTokens()) { + System.err.println("Bad format "+msg); + continue; + } + int version = Integer.parseInt( st.nextToken() ); + if ( version != bus.PROCOCOLVERSION ) { + System.err.println("Ignoring bad protocol version broadcast"); + continue; + } + if ( ! st.hasMoreTokens()) { + System.err.println("Bad format "+msg); + continue; + } + port = Integer.parseInt( st.nextToken() ); + if ( (bus.applicationPort == port) ) continue; + traceDebug("BUSWATCHER Broadcast de " + +packet.getAddress().getHostName() + +":"+packet.getPort()+" port "+port+" version "+version); + try { + Socket socket = new Socket( remotehost, port ); + bus.addClient(socket,false); + } catch ( UnknownHostException e ) { + System.err.println("Unkonwn host "+remotehost + e.getMessage()); + } catch ( IOException e) { + System.err.println("can't connect to "+remotehost+" port "+ + port+e.getMessage()); + } + } catch ( IOException e ) { + watcherrunning=false; + traceDebug("broadcast listener crashed " + e.getMessage()); + } broadcast.close(); + traceDebug("broadcast listener normal shutdown"); } - private void sendBroadcast( String data, String net ) throws IvyException - { - DatagramPacket packet; - // transformation en forme de broadcast w.x.y.z en w.x.y.255 - try { - // simple trick to expend to 255 thank's Alex - net += ".255.255.255"; - RE exp = new RE( "^(\\d+\\.\\d+\\.\\d+\\.\\d+).*"); - net = exp.substitute( net , "$1" ); - } catch ( REException e ){ - throw new IvyException("Bad broascat addr"); - } - // broadcast sur l'adresse iadr + + /** + * stops the thread waiting on the broadcast socket + */ + void stop() { watcherrunning=false; } + + private void sendBroadcast(String data, String net) throws IvyException { try { - InetAddress iaddr = InetAddress.getByName(net); - domainaddrList.addElement( iaddr ); - packet = new DatagramPacket( + // simple trick to expand to 255 (Alex Bustico) + net += ".255.255.255"; + RE exp = new RE( "^(\\d+\\.\\d+\\.\\d+\\.\\d+).*"); + net = exp.substitute( net , "$1" ); + } catch ( REException e ){ + throw new IvyException("Bad broascat addr"); + } + try { + InetAddress iaddr = InetAddress.getByName(net); + domainaddrList.addElement(iaddr); + DatagramPacket packet = new DatagramPacket( data.getBytes(), data.length(), iaddr, broadcast.getLocalPort() ); - broadcast.send( packet ); + broadcast.send(packet); } catch ( UnknownHostException e ) { - throw new IvyException("Broadcast sur réseau inconnu " + e ); + throw new IvyException("Broadcast sent on unknown network "+ + e.getMessage()); } catch ( IOException e ) { - throw new IvyException("broadcast send erreur " + e ); + throw new IvyException("Broadcast error " + e.getMessage() ); } } - public void sendStart(String domain) throws IvyException { - String domainaddr; - // parse Domain to get port number - int port; - int sep_index = domain.lastIndexOf( ":" ); - if ( sep_index == -1 ) - { - port = bus.DEFAULT_PORT; - domainaddr = domain; - } - else - { - port = Integer.parseInt( domain.substring( sep_index +1 )); - domainaddr = domain.substring(0,sep_index); - } - // create the UDP socket - try { + + void start(String domain) throws IvyException { + String domainaddr; + // parse Domain to get port number + int port; + int sep_index = domain.lastIndexOf( ":" ); + if ( sep_index == -1 ) { + port = bus.DEFAULT_PORT; + domainaddr = domain; + } else { + port = Integer.parseInt( domain.substring( sep_index +1 )); + domainaddr = domain.substring(0,sep_index); + } + // create the UDP socket + try { broadcast = new MulticastSocket(port ); } catch ( IOException e ) { throw new IvyException("IvyWatcher erreur I/O" + e ); } - // start UDP receiver - start(); - // send hello world on UDP sockets - String hello = "3 "+bus.getApplicationPort()+"\n"; - StringTokenizer st = new StringTokenizer(domainaddr," \t:,"); - while ( st.hasMoreTokens()) { - sendBroadcast( hello, st.nextToken() ); - } - } - - IvyWatcher(Ivy bus) throws IvyException { - this.bus = bus; - domainaddrList = new Vector(); + // starts a Thread listening on the socket + watcherrunning=true; + broadcastListener = new Thread(this); + broadcastListener.start(); + // notifies our arrival on each domain: protocol version + port + String hello = bus.PROCOCOLVERSION + " " + bus.applicationPort + "\n"; + StringTokenizer st = new StringTokenizer(domainaddr," \t:,"); + while ( st.hasMoreTokens()) { sendBroadcast( hello, st.nextToken() ); } } - - public void run() { - DatagramPacket packet; - byte buf[] = new byte[256]; - int port; - int version; - /**/ - traceDebug("IvyWatcher waiting for Broadcast"); - while( bus.ivyRunning() ) - { - try { - /* receive antoher application port */ - packet = new DatagramPacket(buf, 256); - broadcast.receive( packet ); - String msg = new String(packet.getData()) ; - InetAddress remotehost = packet.getAddress(); - traceDebug("BUSWATCHER Receive Broadcast from:"+ - remotehost.getHostName()+ - ":"+packet.getPort() - +"'"+msg+"'"); - // check if remoteaddr is in our broadcast domain list - if ( !isInDomain( remotehost ) ) - continue; - StringTokenizer st = new StringTokenizer(msg); - if ( ! st.hasMoreTokens()) { - System.err.println("Bad format "+msg); - continue; - } - version = Integer.parseInt( st.nextToken() ); - if ( ! st.hasMoreTokens()) { - System.err.println("Bad format "+msg); - continue; - } - port = Integer.parseInt( st.nextToken() ); - // TODO: More checking here host port domain etc.... - if ( (bus.getApplicationPort() == port) ) - { - continue; - } - traceDebug("BUSWATCHER Broadcast de " - +packet.getAddress().getHostName()+":"+packet.getPort()+" port "+port); - /* connect to the application */ - try { - Socket socket = new Socket( remotehost, port ); - bus.addClient( socket ); - } catch ( UnknownHostException e ) { - System.err.println("Unkonwn host "+remotehost); - } catch ( IOException e) { - System.err.println("can't connect to "+remotehost+" port "+port); - } - - } catch ( IOException e ) { - // TODO: comment distinger la fin normale de 'erreur - /* fermeture de la socket arret normal */ - traceDebug("Error IvyWatcher exception: " + e.getMessage()); + + private boolean isInDomain( InetAddress host ){ + byte rem_addr[] = host.getAddress(); + for ( int i = 0 ; i < domainaddrList.size(); i++ ) { + byte addr[] = ((InetAddress)domainaddrList.elementAt(i)).getAddress(); + int j ; + for ( j = 0 ; j < 4 ; j++ ) + if ( (addr[j] != -1) && (addr[j] != rem_addr[j]) ) break; + if ( j == 4 ) { + traceDebug( "host " + host + " is in domain\n" ); + return true; + } } - }// end while + traceDebug( "host " + host + " Not in domain\n" ); + return false; } - + private void traceDebug(String s){ if (debug) System.out.println("-->ivywatcher<-- "+s); } - private boolean isInDomain( InetAddress host ){ - byte rem_addr[] = host.getAddress(); - for ( int i = 0 ; i < domainaddrList.size(); i++ ) - { - byte addr[] = ((InetAddress)domainaddrList.elementAt(i)).getAddress(); - int j ; - for ( j = 0 ; j < 4 ; j++ ) - if ( (addr[j] != -1) && (addr[j] != rem_addr[j]) ) break; - if ( j == 4 ) - { - traceDebug( "host " + host + " is in domain\n" ); - return true; - } - } - traceDebug( "host " + host + " Not in domain\n" ); - return false; - } -} + +} // class IvyWatcher +/* EOF */ diff --git a/src/Probe.java b/src/Probe.java new file mode 100644 index 0000000..6218e03 --- /dev/null +++ b/src/Probe.java @@ -0,0 +1,77 @@ +package fr.dgac.ivy ; +import gnu.getopt.Getopt; +import java.io.*; + +/** + * terminal implementation in java of the ivyprobe. + * For a graphical version, see TestIvySwing + * @see fr.dgac.ivy.TestIvySwing + * @author Yannick Jestin + * @author http://www.tls.cena.fr/products/ivy/ + */ +class Probe implements IvyApplicationListener, IvyMessageListener { + + public static void main(String[] args) { + Probe p = new Probe(); + Getopt opt = new Getopt("Probe",args,"b:d"); + int c; + String domain=Ivy.DEFAULT_DOMAIN; + while ((c = opt.getopt()) != -1) switch (c) { + case 'b': + domain=opt.getOptarg(); + break; + case 'd': + System.setProperty("IVY_DEBUG","yes"); + break; + default: + } + // connexion to the Bus + Ivy bus; + try { + bus=new Ivy("JPROBE","JPROBE ready",p); + for (int i=opt.getOptind();i Sent to " +bus.sendMsg(s)+" peers"); + } catch (IvyException ie) { + System.out.println("Caught an exception. quitting. "+ie.getMessage()); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public void connect(IvyClient client) { + System.out.println(client.getApplicationName() + " connected " ); + for (java.util.Enumeration e=client.getRegexps();e.hasMoreElements();) + System.out.println(client.getApplicationName() + " subscribes to " + +e.nextElement() ); + } + + public void disconnect(IvyClient client) { + System.out.println(client.getApplicationName() + " disconnected " ); + } + + public void die(IvyClient client, int id) { + System.out.println(client.getApplicationName() + " die "+ id ); + } + + public void directMessage(IvyClient client, int id, String arg) { + System.out.println(client.getApplicationName() + " direct Message "+ id + arg ); + } + + public void receive(IvyClient client, String[] args) { + String s=client.getApplicationName() + " sent"; + for (int i=0;ihttp://www.tls.cena.fr/products/ivy/ + */ + +class TestIvy extends Panel implements IvyApplicationListener { + private Ivy bus ; + private String regexp=""; + private TextField tfRegex, tfSend ; + private TextArea ta ; + private Button buApply, buSend, buClear ; + private java.text.SimpleDateFormat format = new + java.text.SimpleDateFormat("hh:mm:ss"); + private int regexp_id; - Ivy bus ; - String regexp ; - TextField tfRegex, tfSend ; - TextArea ta ; - Button buApply, buSend, buClear ; - int regexp_id; public TestIvy() throws IvyException { super(new BorderLayout()); - regexp = ""; - // un textarea au centre ta = new TextArea(); ta.setEditable(false); add(ta,BorderLayout.CENTER); - // une zone regex au nord Panel p = new Panel(new BorderLayout()); p.add(new Label("Regex:"),BorderLayout.WEST); tfRegex = new TextField(regexp); tfRegex.addActionListener(new REGCB()); p.add(tfRegex,BorderLayout.CENTER); add(p,BorderLayout.NORTH); - // une zone messages au sud p = new Panel(new BorderLayout()); p.add(new Label("Msg:"),BorderLayout.WEST); tfSend = new TextField(""); tfSend.addActionListener(new SENDCB()); p.add(tfSend,BorderLayout.CENTER); add(p,BorderLayout.SOUTH); - bus = new Ivy("JAVA TESTBUS","Testbus ridi",this); - bus.start(null); - append( "Ivy Domain: "+ bus.getDomain(null) ); + bus = new Ivy("JAVA TESTBUS","Testbus is ready",this); + bus.start(null); + append( "Ivy Domain: "+ bus.getDomain(null) ); + } + + public static void main(String[] args) throws IvyException { + TestIvy tb = new TestIvy(); + Frame f = new Frame("TestIvy"); + f.addWindowListener( tb.new WCCB(f,tb)) ; + f.add(tb, BorderLayout.CENTER); + f.pack(); + f.setVisible(true); } - public void stop() { - bus.stop(); - } public void connect(IvyClient client) { - append(client.getApplicationName() + " connected " ); + append(client.getApplicationName() + " connected " ); } + public void disconnect(IvyClient client) { append(client.getApplicationName() + " disconnected " ); } @@ -52,64 +68,53 @@ class TestIvy extends Panel implements IvyApplicationListener{ public void directMessage(IvyClient client, int id, String arg) { append(client.getApplicationName() + " direct Message "+ id + arg ); } - /* inner */ class REGCB implements ActionListener, IvyMessageListener { + + class REGCB implements ActionListener, IvyMessageListener { public void actionPerformed(ActionEvent e) { - // enlever l'ancienne regex - bus.unBindMsg(regexp_id); - // ajoute la nouvelle regex - regexp=tfRegex.getText(); - regexp.trim(); + try { + bus.unBindMsg(regexp_id); + } catch (IvyException ie) { + System.out.println("Big badaboum"); // this should not happen + } + regexp=tfRegex.getText(); + regexp.trim(); regexp_id = bus.bindMsg(regexp,this); - tfRegex.setText(""); + tfRegex.setText(""); + } + public void receive(IvyClient client, String[] args) { + String out="client " + client.getApplicationName() + " envoie: [ "; + for (int i=0;ihttp://www.tls.cena.fr/products/ivy/ + */ class TestIvySwing extends JPanel implements IvyApplicationListener { + + private static String version ="1.0.2"; private static int index; private static int nbTIS=0; - String localname; - - Ivy bus ; - String regexp = "^AIRCRAFT:";; - JLabel laRegex; - JTextArea ta ; - JTextField tfRegex, tfSend ; - JButton buApply, buSend, buClear ; - JComboBox ports; - int regexp_id; - REGCB reg; - - public TestIvySwing() throws IvyException { + private String localname; + private Ivy bus ; + private String regexp = "(.*)"; + private JLabel laRegex; + private JTextArea ta ; + private JTextField tfRegex, tfSend ; + private JButton buApply, buSend, buClear ; + private JComboBox ports; + private int regexp_id; + private REGCB reg; + private java.text.SimpleDateFormat format = new + java.text.SimpleDateFormat("hh:mm:ss"); + private static String[] startDomainList = { + "127.255.255.255:2010", + "10.192.36.255:2223", + "10.192.36.255:2333", + "10.192.36.255:2020", + "10.192.36.255:3110", + }; + private static java.util.Vector domainList; + + static { + // initialize the domainlist + domainList = new java.util.Vector(); + for (int i = 0; i