/// 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
}
}