/// François-Régis Colin
/// http://www.tls.cena.fr/products/ivy/
/// *
/// (C) CENA
/// *
namespace IvyBus
{
using System;
using System.Threading;
using System.IO;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using System.Configuration;
using System.Text;
using System.Diagnostics;
using IvyBus.Properties;
/// IvyWatcher, A private Class for the Ivy rendezvous
///
/// right now, the rendez vous is either an UDP socket or a TCP multicast.
/// 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.
///
internal class IvyWatcher : IDisposable
{
private Ivy bus; /* master bus controler */
private int port;
private volatile Thread listenThread;
private IPAddress group;
private IvyUDPStream stream;
private bool ipv6;
/// creates an Ivy watcher
///
/// the bus
///
/// the domain
///
/// the port number
///
internal IvyWatcher(Ivy bus, String domainaddr, int port, bool _ipv6)
{
int multicast_ttl = 64; // region
this.bus = bus;
this.port = port;
this.ipv6 = _ipv6;
listenThread = new Thread(new ThreadStart(this.Run));
listenThread.Name = "Ivy UDP Listener Thread";
try
{
group = IPAddress.Parse(domainaddr);
/* supervision socket */
// To do reuseaddr we must use a Socket not a udp client
Socket broadcast = new Socket(ipv6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
IPEndPoint EPhost = new IPEndPoint(ipv6 ? IPAddress.IPv6Any : IPAddress.Any, port);
broadcast.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.Broadcast,true);
broadcast.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReuseAddress,true);
broadcast.Bind(EPhost);
//test isMulticastAddress // TODO better check
if (group.IsIPv6Multicast) //IPV6 multicast
{
broadcast.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.MulticastTimeToLive, multicast_ttl);
broadcast.SetSocketOption(SocketOptionLevel.IPv6, SocketOptionName.AddMembership, new IPv6MulticastOption(group));
}
else
{
byte[] addr = group.GetAddressBytes();//yes but in IPV4 how to do better
if ((addr[0] & 0xf0) == 0xe0)
{
broadcast.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.MulticastTimeToLive, multicast_ttl);
broadcast.SetSocketOption(SocketOptionLevel.IP, SocketOptionName.AddMembership, new MulticastOption(group));
}
}
// TODO support the Two protocol
if (bus.protocolVersion == 4)
stream = new IvyUDPStreamV4(broadcast);
else
stream = new IvyUDPStreamV3(broadcast);
}
catch (IOException e)
{
throw new IvyException(Resources.WatcherIOError + e);
}
}
///
/// the behaviour of each thread watching the UDP socket.
///
private void Run()
{
Ivy.traceProtocol(Resources.IvyWatcher, "beginning of a watcher Thread");
try
{
bool running = true;
while (running)
{
int version;
int appPort;
string appId;
string appName;
IPEndPoint remoteEP;
stream.receiveMsg(out remoteEP, out version, out appPort, out appId, out appName);
IPAddress remotehost = remoteEP.Address;
Ivy.traceProtocol(Resources.IvyWatcher, Resources.WatcherReceive + Dns.GetHostEntry(remotehost).HostName + ":" + remoteEP.Port);
//TODO if ( !isInDomain( remotehost ) ) continue;
if (version != stream.ProtocolVersion)
{
Ivy.traceError(Resources.IvyWatcher, Resources.BadVersion + version + " expected " + stream.ProtocolVersion);
continue;
}
/* check if we received our own message. SHOULD ALSO TEST THE HOST */
if (appId == bus.AppId)
continue;
if ((appPort == bus.applicationPort) && (remotehost.Equals(bus.applicationHost)))
continue;
Ivy.traceProtocol(Resources.IvyWatcher, "reponse au Broadcast de " + Dns.GetHostEntry(remotehost).HostName + ":" + remoteEP.Port + " port " + appPort +
" version " + version +
" id " + appId +
" name " + appName);
try
{
Socket socket = new Socket(ipv6 ? AddressFamily.InterNetworkV6 : AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPEndPoint hostEndPoint = new IPEndPoint(remoteEP.Address, appPort);
socket.Blocking = true;
socket.Connect(hostEndPoint);
IvyClient client = new IvyClient(this.bus, socket, appName, appPort);
client.SendBindings();
}
catch (SocketException e)
{
Ivy.traceError(Resources.IvyWatcher, Resources.WatcherConnectError + remotehost + " port " + appPort + " \n" + e.Message);
}
} // while
}
catch (ObjectDisposedException ex)
{
Ivy.traceError(Resources.IvyWatcher, Resources.WatcherSocketClosed + ex.Message);
}
catch (SocketException se)
{
Ivy.traceError(Resources.IvyWatcher, Resources.WatcherSocketClosed + se.Message);
}
catch (IOException ioe)
{
Ivy.traceError(Resources.IvyWatcher, Resources.WatcherIOException + ioe.Message);
}
Ivy.traceProtocol(Resources.IvyWatcher, "end of a watcher thread");
}
/// stops the thread waiting on the broadcast socket
///
internal virtual void stop()
{
lock (stream)
{
Ivy.traceProtocol(Resources.IvyWatcher, "begining stopping an IvyWatcher");
stream.Close();
if (listenThread != null)
{
// Wait for Thread to end.
bool term = listenThread.Join(10000);
if (!term && (listenThread != null)) listenThread.Abort();
listenThread = null;
}
// it might not even have been created
Ivy.traceProtocol(Resources.IvyWatcher, "ending stopping an IvyWatcher");
}
}
///
/// send the boradcst message on all domains
///
internal virtual void start()
{
lock (stream)
{
listenThread.Start();
IPEndPoint EPhost = new IPEndPoint(group, port);
stream.sendMsg(EPhost, bus.applicationPort, bus.AppId, bus.AppName);// notifies our arrival on each domain: protocol version + port
}
}
// needed for IvyUDPStreamV4', 'IvyUDPStreamV3 managed object ??!!!
#region IDisposable Membres
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
protected virtual void Dispose(bool disposing)
{
if (disposing)
{
// Free other state (managed objects).
if (stream != null)
{
stream.Close();
stream = null;
}
}
// Free your own state (unmanaged objects).
// Set large fields to null.
}
#endregion
}
}