IntroductionIn the
previous article I showed how to create a Broadcast Event Server. The clients could send messages through the server to all the other connected clients.
One of the problems you have maybe seen is that if the client starts and the server is not working, than you will get an exception: "Additional information: No connection could be made because the target machine actively refused it".
On this tutorial I will show how to improve our Client/Server system by automatically connect the clients to the server once the server starts, get notifications about connectivity problems, and recover the connection.
Download
Full Source Code (~300KB)
New: Download the Full Source Code in
VB (~22KB)
Step 1 – Create a ServerConnector ClassFirst we add another interface to Common project. This interface is IServerConnector interface:
------------------------------------------------------------
namespace Common
{
public interface IServerConnector
{
bool IsAlive
{
get;
}
}
}
-------------------------------------------------------------
As you can see the interface has one read-only property IsAlive. This property will be called by the client periodically in order to check if the server is running.
Next we add a class called ServerConnector to the Server project:
-------------------------------------------------------------
using System;
using Common;
namespace Server
{
[Serializable()]
public class ServerConnector:MarshalByRefObject,IServerConnector
{
public bool IsAlive
{
get { return true; }
}
public override object InitializeLifetimeService()
{
return null;
}
}
}
---------------------------------------------------------------
This class will be called remotely by the client once in a while. If the return value is true then all is well. If the client gets an exception, then the server is not functioning.
Step 2 – Create a ClientConnector ClassNow that we have our ServerConnector class, we need to call the IsAlive method. We create a class that will have a Timer, and every period of time we will call ServerConnector. The class will have two events: ServerConnected and ServerDisconnected. This events can be used later by other objects in the client in order to provide functionality in those two states.
Add the following ClientConnector class to the Client project:
-------------------------------------------------------------
using System;
using System.Threading;
using System.Timers;
using Common;
using Timer = System.Timers.Timer;
namespace Client
{
public class ClientConnector
{
public ClientConnector()
{
_serverConnectivityTimer = new Timer(1000);
_serverConnectivityTimer.Elapsed += new ElapsedEventHandler (serverConnectivityTimer_Elapsed);
_serverConnectivityTimer.Start();
}
public event EventHandler ServerDisconnected;
public event EventHandler ServerReconnect;
private Timer _serverConnectivityTimer;
private void serverConnectivityTimer_Elapsed(object sender, ElapsedEventArgs e)
{
_serverConnectivityTimer.Stop();
CheckServerConnectivity();
_serverConnectivityTimer.Start();
}
private bool _serverIsAlive = false;
private void CheckServerConnectivity()
{
try
{
IServerConnector serverConnector = (IServerConnector) RemotingHelper. GetObject(typeof(IServerConnector));
bool isAlive = serverConnector.IsAlive;
if (isAlive && !_serverIsAlive)
SetServerAlive();
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
Thread t = new Thread(new ThreadStart (SetServerNotAlive));
t.Start();
t.Join();
}
}
private void SetServerAlive()
{
if (!_serverIsAlive && ServerReconnect != null)
ServerReconnect(this,EventArgs.Empty);
_serverIsAlive = true;
}
public void SetServerNotAlive()
{
if (_serverIsAlive && ServerDisconnected != null)
ServerDisconnected(this,EventArgs.Empty);
_serverIsAlive = false;
}
public bool ServerAlive
{
get
{
_serverConnectivityTimer.Stop();
CheckServerConnectivity();
_serverConnectivityTimer.Start();
return _serverIsAlive;
}
}
}
}
-------------------------------------------------------------
The timer elapses every second. A call to ServerConnector.IsAlive is done. If no exception was thrown, the server is connected (we raise the ServerReconnected event only if the state has changed). If an exception was thrown, we know that the server is not connected, and we can raise the ServerDisconnected event (only if soemthing has changed).
We now have a nice generic class to tell us when the server connects and when it disconnects.
Step 3 – Hook things upFirst we need to register our remote objects in the configuration file.
Add the following line to the Server App.Config (inside the service tags):
<wellknown mode="Singleton" type="Server.ServerConnector, Server" objectUri= "ServerConnector.soap"/>
Add the following line to the Client App.Config (inside the client tags):
<wellknown type="Common.IServerConnector, Common" url= "tcp://localhost:16784/EventServer/ ServerConnector.soap"/>
Second, we change the ClientStartup class to contain an instance of ClientConnector. This will be the instance that all objects in the client will use:
-------------------------------------------------------------
using System;
using System.Runtime.Remoting;
using System.Windows.Forms;
namespace Client
{
public class ClientStartup
{
private static ClientConnector _connector;
public static ClientConnector Connector
{
get
{ return _connector; }
}
[STAThread]
static void Main()
{
_connector = new ClientConnector();
InitRemoting();
Application.Run(new ClientForm());
}
private static void InitRemoting()
{
string fileName = "client.exe.config";
RemotingConfiguration.Configure(fileName);
}
}
}
-------------------------------------------------------------
Next, we add changes to MessageBroadcaster in the client. We want the class to get the singleton objects from the server when the server reconnects (since we loose them when the server disconnects). Additionally we re-set the events (Once the server disconnects we loose the events).
-------------------------------------------------------------
using System;
using System.Net.Sockets;
using System.Threading;
using Common;
namespace Client
{
public class MessageBroadcaster:IBroadcaster
{
private IBroadcaster _bcaster = null;
private BroadcastEventHelper _eventHelper = null;
public MessageBroadcaster()
{
if (ClientStartup.Connector.ServerAlive)
{
InitializeObjects();
}
ClientStartup.Connector.ServerDisconnected += new EventHandler (Connector_ServerDisconnected);
ClientStartup.Connector.ServerReconnect += new EventHandler (Connector_ServerReconnect);
}
public void BroadcastMessage(Message msg)
{
if (_bcaster == null)
throw new ServerNotAliveException();
try
{
_bcaster.BroadcastMessage(msg);
}
catch (SocketException ex)
{
Console.WriteLine(ex.Message);
}
catch (ThreadAbortException ex)
{
Console.WriteLine(ex.Message);
}
}
private void HandleMessage(Message msg)
{
if (MessageArrived != null)
MessageArrived(msg);
}
private void InitializeObjects()
{
_bcaster = (IBroadcaster)RemotingHelper. GetObject(typeof(IBroadcaster));
_eventHelper = new BroadcastEventHelper();
_eventHelper.MessageArrivedLocally += new MessageArrivedHandler HandleMessage);
_bcaster.MessageArrived += new MessageArrivedHandler (_eventHelper.LocallyHandleMessageArrived);
}
private void Connector_ServerDisconnected(object sender, EventArgs e)
{
_bcaster = null;
_eventHelper = null;
}
private void Connector_ServerReconnect(object sender, EventArgs e)
{
InitializeObjects();
}
public event MessageArrivedHandler MessageArrived;
}
}
-------------------------------------------------------------
Step 4 – Fix the DemoIf you want to see the changes, you need to do the following modification to the ClientForm:
Change the constructor of the ClientForm:
public ClientForm()
{
InitializeComponent();
_messageBroadcaster.MessageArrived += new Common.MessageArrivedHandler (broadcaster_MessageArrived);
ClientStartup.Connector.ServerDisconnected += new EventHandler (Connector_ServerDisconnected);
ClientStartup.Connector.ServerReconnect += new EventHandler (Connector_ServerReconnect);
if (ClientStartup.Connector.ServerAlive)
this.Text = "Client Connected";
else
this.Text = "Client Not Connected";
}
Add the following event handlers to the form:
private void Connector_ServerDisconnected(object sender, EventArgs e)
{
this.Text = "Client Not Connected";
}
private void Connector_ServerReconnect(object sender, EventArgs e)
{
this.Text = "Client Connected";
}
Now you can run the server or the clients in any order you wish. If the client starts and the server is not started you will see it in the Text of the client form. If the server disconnects and reconnects you also see it in the clients. And most importantly – the client and server continue to function as they should.