From 92757a8d629812303ff3665343bd098917cca611 Mon Sep 17 00:00:00 2001 From: fcolin Date: Thu, 1 Feb 2007 12:04:16 +0000 Subject: modification structure svn --- Ivy/IvyClient.cs | 592 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 592 insertions(+) create mode 100644 Ivy/IvyClient.cs (limited to 'Ivy/IvyClient.cs') diff --git a/Ivy/IvyClient.cs b/Ivy/IvyClient.cs new file mode 100644 index 0000000..3db9f0f --- /dev/null +++ b/Ivy/IvyClient.cs @@ -0,0 +1,592 @@ +/// François-Régis Colin +/// http://www.tls.cena.fr/products/ivy/ +/// * +/// (C) CENA +/// * +namespace IvyBus +{ + using System; + using System.Collections; + using System.Collections.Specialized; + using System.Collections.Generic; + using System.Threading; + using System.Text; + using System.IO; + using System.Net; + using System.Net.Sockets; + using System.Configuration; + using System.Diagnostics; + + /// A Class for the the peers on the bus. + /// + /// + /// 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. + /// + public class IvyClient : IvyProtocol, IComparable, IDisposable + { + public int CompareTo(IvyClient other) + { + return (other.clientPriority - clientPriority); + } + + public String ApplicationName + { + get + { + return appName; + } + + } + + public List Regexps + { + get + { + List tab = new List(); + lock (bindings) + { + foreach (IvyBindingBase bind in bindings.Values) + tab.Add(bind.Expression); + } + return tab; + } + + } + internal int AppPort + { + get + { + return appPort; + } + + } + public IPAddress RemoteAddress + { + get + { + return remoteHost; + } + + } + public int RemotePort + { + get + { + return remotePort; + } + + } + + private Ivy bus; + private Dictionary bindings; + private int appPort; + private string clientId; /* an unique ID for each IvyClient */ + private int clientPriority; /* client priority */ + + private volatile Thread clientThread; // volatile to ensure the quick communication + private bool doping; // false by runtime default + private const int PINGTIMEOUT = 5000; + private volatile Thread pingerThread; + + private int remotePort; + private IPAddress remoteHost; + + // protected variables + internal String appName; + internal IvyProtocol stream; + + internal IvyClient(Ivy bus, Socket socket, string appname) + { + bindings = new Dictionary(); + appName = appname; + this.bus = bus; + + IPEndPoint endpoint = (IPEndPoint)socket.RemoteEndPoint; + + remoteHost = endpoint.Address; + remotePort = endpoint.Port; + +#if (!PocketPC ) + socket.SetSocketOption( SocketOptionLevel.Tcp, SocketOptionName.KeepAlive, 1 ); +#endif + + if ( bus.ProtocolVersion == 4 ) + stream = new IvyTCPStreamV4( socket, this ); + else + stream = new IvyTCPStreamV3(socket, this); + + clientPriority = Ivy.DEFAULT_PRIORITY; + // spawns a thread to manage the incoming traffic on this + // socket. We should be ready to receive messages now. + clientThread = new Thread(new ThreadStart(this.Run)); + clientThread.Name = "Ivy Tcp Client Reader Thread ("+appname+")"; + clientThread.Start(); + + } + + internal void SendBindings() + { + try + { + stream.TokenApplicationId(bus.applicationPriority, bus.AppId); + + // 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 + stream.TokenStartRegexp(bus.applicationPort, bus.appName); + // sends our regexps to the peer + lock (bus.bindings) + { + foreach (IvyApplicationBinding bind in bus.bindings.Values) + { + stream.TokenAddBinding(bind.Binding, bind.Key, bind.FormatedExpression); + } + } + stream.TokenEndRegexp(); + +#if (!PocketPC) + doping = Properties.Settings.Default.IvyPing; +#endif + if (doping) + { + pingerThread = new Thread(new ThreadStart(PingerRun)); + pingerThread.Name = "Ivy Pinger Thread"; + pingerThread.Start(); + } + + + } + catch (IOException ex) + { // the client nous a coupé l'herbe sous le pied + Ivy.traceError("IvyClient","I can't send my message to this client. He probably left " + ex.Message); + // invokes the Disconnected applicationListeners + //bus.OnClientDisconnected(new IvyEventArgs(this,id, message )); + // called by the receiver Thread + close(false); + } + } + + /// returns the name of the remote agent. + /// 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 + /// + + /// sends a direct message to the peer + /// + /// the numeric value provided to the remote client + /// + /// the string that will be match-tested + /// + /// + public void SendDirectMsg(ushort id, string message) + { + try + { + stream.TokenDirectMsg( id, message); + } + catch (IOException ex) + { + Ivy.traceError("IvyClient","I can't send my message to this client. He probably left "+ex.Message); + // first, I'm not a first class IvyClient any more + bus.removeClient(this); + // invokes the Disconnected applicationListeners + //bus.OnClientDisconnected(new IvyEventArgs(this,id, message )); + // should be called by receiver thread + close(false); + } + } + + /// closes the connexion to the peer. + /// + /// should I send Bye message ? + /// the thread managing the socket is stopped + /// + /// + internal void close(bool notify) + { + Ivy.traceProtocol("IvyClient","closing connexion to " + appName); + if (doping ) + { + StopPinging(); + } + if (notify) + try + { + stream.TokenBye(0, "hasta la vista"); + } + catch (IOException ioe) + { + throw new IvyException(ioe.Message); + } + // stop the thread and close the stream + if (clientThread == null) + return; + // Tell Thread to stop. + if (stream != null) + { + try + { + stream.Close(); // should stop the Reading Client Thread + } + catch (IOException ioe) + { + throw new IvyException(ioe.Message); + } + //socket.Close(); // pris en charge par stream ( NetWorkStream ) + stream = null; + } + // Potential dead lok when thread issue ClientDisconnected event + //if (Thread.CurrentThread != clientThread && (clientThread != null)) + //{ + // // Wait for Thread to end. + // bool term = clientThread.Join(10000); + // if (!term && (clientThread != null)) clientThread.Abort(); + //} + + clientThread = null; + + + } + + /// sends the substrings of a message to the peer for each matching regexp. + /// + /// the string that will be match-tested + /// + /// the number of messages sent to the peer + /// + /// + internal int sendMsg(String message) + { + int count = 0; + + lock( bindings ) + { + try + { + foreach (IvyBindingBase bind in bindings.Values) + { + string[] args = bind.Match(message); + if (stream != null && args != null) + { + stream.TokenMsg(bind.Key, args); + count++; + } + } + } + catch (IOException ex) + { + Ivy.traceError("IvyClient","I can't send my message to this client. He probably left " + ex.Message); + // first, I'm not a first class IvyClient any more + bus.removeClient(this); + // invokes the Disconnected applicationListeners + // in the receiver thread + close(false); + } + + + } + return count; + } + + /// compares two peers the id is the couple (host,service port). + /// + /// the other peer + /// + /// true if the peers are similir. This should not happen, it is bad + /// © ® (tm) + /// + /// + internal bool sameClient(IvyClient clnt) + { + return (appPort != 0 && appPort == clnt.appPort) && (RemoteAddress == clnt.RemoteAddress); + } + + /// the code of the thread handling the incoming messages. + /// + private void Run() + { + Ivy.traceProtocol("IvyClient","Connected from " + RemoteAddress + ":" + RemotePort); + + Ivy.traceProtocol("IvyClient","Thread started"); + + bool running = true; + while ( running && (stream != null) ) + { + try + { + if ( stream.receiveMsg() ) + { + // early stop during readLine() + if (doping && (pingerThread != null)) + pingerThread.Abort(); + } + else + { + Ivy.traceProtocol("IvyClient","receiveMsg false ! leaving the thread"); + running = false; + break; + } + } + catch ( ObjectDisposedException ex ) + { + Ivy.traceError("IvyClient", "socket closed "+ex.Message ); + running = false; + break; + } + catch (IvyException ie) + { + Ivy.traceError("IvyClient","socket closed IvyException" + ie.Message); + running = false; + break; + } + catch (SocketException se) + { + Ivy.traceError("IvyClient", "socket closed "+se.Message ); + running = false; + break; + } + catch (IOException ex) + { + if ( ex.InnerException is SocketException ) + { + Ivy.traceProtocol("IvyClient", "socket closed" ); + + } + else + { + Ivy.traceError("IvyClient","abnormally Disconnected from " + RemoteAddress + ":" + RemotePort); + } + running = false; + break; + } + } + Ivy.traceProtocol("IvyClient","normally Disconnected from " + appName); + Ivy.traceProtocol("IvyClient","Thread stopped"); + // invokes the Disconnected applicationListeners + bus.OnClientDisconnected(new IvyEventArgs(this,0, "" )); + // first, I'm not a first class IvyClient any more + if (stream != null) + { + stream.Close(); + stream = null; + } + bus.removeClient(this); + + } + void IvyProtocol.Close() + { + // never call in this side + } + bool IvyProtocol.receiveMsg() + { + // nerver call in this side + return false; + } + void IvyProtocol.TokenDie(ushort id, string arg) + { + Ivy.traceProtocol("IvyClient","received die Message from " + appName + "Raison: "+ arg); + // invokes the die applicationListeners + IvyDieEventArgs ev = new IvyDieEventArgs(this, id, arg); + bus.OnDie(ev); + // first, I'm not a first class IvyClient any more + bus.removeClient(this); + // makes the bus die + bus.Stop(); + close(false); + if (ev.ForceExit) +#if (PocketPC) + System.Windows.Forms.Application.Exit(); +#else + System.Environment.Exit(0); +#endif + } + void IvyProtocol.TokenBye(ushort err, string arg) + { + // the peer quits + Ivy.traceProtocol("IvyClient","received bye Message from " + appName + "Raison: "+ arg); + // first, I'm not a first class IvyClient any more + //bus.removeClient(this); // this is done in the receive thread terminaison!! + // invokes the disconnect applicationListeners + //bus.FireClientDisconnected(this); done in Running Thread + close(false); // will fire disconnected + } + + void IvyProtocol.TokenAddBinding(BindingType type, ushort id, string expression) + { + + if (type == BindingType.Regexp && !bus.CheckRegexp(expression)) + { + bus.OnClientFilterBinding(new IvyEventArgs(this, id, expression )); + return; + } + IvyBindingBase bind = null; + try + { + switch (type) + { + case BindingType.Regexp: + bind = new IvyBindingRegexp(id, expression); + break; + case BindingType.Simple: + bind = new IvyBindingSimple(id, expression); + break; + } + lock (bindings) + { + bindings.Add(id, bind); + } + + bus.OnClientAddBinding(new IvyEventArgs(this, id, expression)); + + } + catch (ArgumentException ex) + { + throw new IvyException("binding expression error " + ex.Message); + } + + } + void IvyProtocol.TokenDelBinding(ushort id) + { + lock( bindings ) + { + try + { + IvyBindingBase bind = bindings[id]; + bus.OnClientRemoveBinding(new IvyEventArgs(this, bind.Key, bind.Expression)); + bindings.Remove(id); + } + catch (KeyNotFoundException ex) + { + Ivy.traceError("IvyClient","DelBinding " + ex.Message); + } + } + } + void IvyProtocol.TokenMsg(ushort id, string[] args) + { + bus.OnMessage(new IvyMessageEventArgs(this, id, args)); + } + void IvyProtocol.TokenError(ushort id, string arg) + { + bus.OnError(new IvyEventArgs(this, id, arg)); + Ivy.traceError("IvyClient","Error msg " + id + " " + arg); + } + void IvyProtocol.TokenApplicationId(ushort id, string arg) + { + clientId = arg; + if ( clientPriority != id ) + { + clientPriority = id; + bus.SortClients(); + } + } + void IvyProtocol.TokenEndRegexp() + { + /* + * the peer is perhaps not ready to handle this message + * an assymetric processing should be written + */ + if (bus.ReadyMessage != null) + sendMsg(bus.ReadyMessage); + bus.OnClientConnected(new IvyEventArgs(this, 0, "")); + } + void IvyProtocol.TokenStartRegexp(ushort id, string arg) + { + appName = arg; + appPort = id; + if (bus.checkConnected(this)) + { + close(false); + throw new IvyException("Rare ! A concurrent connect occured"); + } + + } + void IvyProtocol.TokenDirectMsg(ushort id, string arg) + { + bus.OnDirectMessage(new IvyEventArgs(this,id,arg)); + } + void IvyProtocol.TokenPing(string arg) + { + // I receive a ping. I can answer a pong. + Ivy.traceProtocol("IvyClient","Ping msg from " + appName + " : " + arg ); + stream.TokenPong(arg); + } + void IvyProtocol.TokenPong(string arg) + { + Ivy.traceProtocol("IvyClient","Ping msg from " + appName + " : " + arg); + } + + + + public override String ToString() + { + return "IvyClient " + bus.appName + ":" + appName; + } + + /* is the Pinging Thread Runninng */ + internal bool isPinging; + + private void PingerRun() + { + isPinging = true; + Ivy.traceProtocol("IvyClient","Pinger Thread started"); + while (isPinging) + { + try + { + Thread.Sleep(PINGTIMEOUT); + stream.TokenPing("are you here ?"); + } + catch (ThreadAbortException ie) + { + Ivy.traceError("IvyClient","Pinger Thread killed "+ie.Message); + } + } + Ivy.traceProtocol("IvyClient","Pinger Thread stopped"); + } + public virtual void StopPinging() + { + isPinging = false; + //pingerThread.Interrupt(); + pingerThread.Abort(); + } + + #region IDisposable Members + + //Implement IDisposable. + public void Dispose() + { + Dispose(true); + GC.SuppressFinalize(this); + } + + protected virtual void Dispose(bool disposing) + { + if (disposing) + { + // Free other state (managed objects). + } + // Free your own state (unmanaged objects). + // Set large fields to null. + if (stream != null) + { + stream.Close(); + stream = null; + } + } + + // Use C# destructor syntax for finalization code. + ~IvyClient() + { + // Simply call Dispose(false). + Dispose(false); + } + + #endregion + } +} \ No newline at end of file -- cgit v1.1