summaryrefslogtreecommitdiff
path: root/Ivy/IvyClient.cs
diff options
context:
space:
mode:
authorfcolin2007-02-01 12:04:16 +0000
committerfcolin2007-02-01 12:04:16 +0000
commit92757a8d629812303ff3665343bd098917cca611 (patch)
treecd995c9863aa6fc4c32ec5ce247e4c3119eb44a3 /Ivy/IvyClient.cs
parent98ab5d0164040427f7c554febae125686284e2a7 (diff)
downloadivy-csharp-92757a8d629812303ff3665343bd098917cca611.zip
ivy-csharp-92757a8d629812303ff3665343bd098917cca611.tar.gz
ivy-csharp-92757a8d629812303ff3665343bd098917cca611.tar.bz2
ivy-csharp-92757a8d629812303ff3665343bd098917cca611.tar.xz
modification structure svn
Diffstat (limited to 'Ivy/IvyClient.cs')
-rw-r--r--Ivy/IvyClient.cs592
1 files changed, 592 insertions, 0 deletions
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;
+
+ /// <summary> A Class for the the peers on the bus.
+ /// </summary>
+ /// <remarks>
+ /// 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.
+ /// </remarks>
+ public class IvyClient : IvyProtocol, IComparable<IvyClient>, IDisposable
+ {
+ public int CompareTo(IvyClient other)
+ {
+ return (other.clientPriority - clientPriority);
+ }
+
+ public String ApplicationName
+ {
+ get
+ {
+ return appName;
+ }
+
+ }
+
+ public List<string> Regexps
+ {
+ get
+ {
+ List<string> tab = new List<string>();
+ 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<ushort,IvyBindingBase> 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<ushort,IvyBindingBase>();
+ 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);
+ }
+ }
+
+ /// <summary> 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
+ /// </summary>
+
+ /// <summary> sends a direct message to the peer
+ /// </summary>
+ /// <param name='id'>the numeric value provided to the remote client
+ /// </param>
+ /// <param name='message'>the string that will be match-tested
+ ///
+ /// </param>
+ 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);
+ }
+ }
+
+ /// <summary> closes the connexion to the peer.
+ /// </summary>
+ /// <param name='notify'>should I send Bye message ?
+ /// the thread managing the socket is stopped
+ ///
+ /// </param>
+ 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;
+
+
+ }
+
+ /// <summary> sends the substrings of a message to the peer for each matching regexp.
+ /// </summary>
+ /// <param name='message'>the string that will be match-tested
+ /// </param>
+ /// <returns>the number of messages sent to the peer
+ ///
+ /// </returns>
+ 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;
+ }
+
+ /// <summary> compares two peers the id is the couple (host,service port).
+ /// </summary>
+ /// <param name='clnt'>the other peer
+ /// </param>
+ /// <returns>true if the peers are similir. This should not happen, it is bad
+ /// © ® (tm)
+ ///
+ /// </returns>
+ internal bool sameClient(IvyClient clnt)
+ {
+ return (appPort != 0 && appPort == clnt.appPort) && (RemoteAddress == clnt.RemoteAddress);
+ }
+
+ /// <summary> the code of the thread handling the incoming messages.
+ /// </summary>
+ 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