Google
WWW Yariv Hammer's Code Site

Tuesday, December 06, 2005

Recovering From Server/Clients Disconnections in Remoting

Introduction
In 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 Class
First 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 Class
Now 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 up
First 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 Demo
If 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.

15 Comments:

At 2:45 AM, December 19, 2005, Blogger P Durrant said...

do you an this example in VB

i can convert everything except this

_eventHelper.MessageArrivedLocally += new MessageArrivedHandler HandleMessage);
_bcaster.MessageArrived += new MessageArrivedHandler (_eventHelper.LocallyHandleMessageArrived);
}

 
At 9:56 AM, December 19, 2005, Blogger j1hammer said...

Hi p durrant
I hope my post is helpful.
I don't have the code in VB.NET, but I am sure you can figure out how to convert it.

The code you posted can be converted to VB as follows:

AddHandler _eventHelper.MessageArrivedLocally, AddressOf(HandleMessage)

AddHandler _bcaster.MessageArrived, AddressOf(_eventHelper. LocallyHandleMessageArrived)

Yariv Hammer

 
At 3:45 PM, December 19, 2005, Blogger P Durrant said...

This comment has been removed by a blog administrator.

 
At 3:47 PM, December 19, 2005, Blogger P Durrant said...

very helpfull. the bit that got me was
+= new MessageArrivedHandler

but the client side is now workimg, with a few tweeks,
on the serverside i have just one bit which vb is complaining about

if (MessageArrived == null)
return;
MessageArrivedHandler mah=null;
foreach(Delegate del in MessageArrived.GetInvocationList())

in vb i have

For Each del As System.Delegate In MessageArrived.GetInvocationList

but the compiler says massagearrived is and event and cannot be called directly, use raiseevent

if we can solve this last bit i would be quite happy to send over the source to put with the c# source

 
At 5:54 AM, December 20, 2005, Blogger P Durrant said...

cracked it, got it all work

For Each del As System.Delegate In MessageArrived.GetInvocationList

becomes

For Each del As System.Delegate In MessageArrivedevent.GetInvocationList

the 'event' is auto generated by the compiler

 
At 9:00 PM, December 20, 2005, Anonymous Anonymous said...

P Durrant, can you post your vb.net conversion? I have spent hours on this and realized I am having the same problem as you did. I have converted everything but get weird results with the Interfaces ... I think the converters are not working as they should.

Error 7 Class 'ServerConnector' must implement 'ReadOnly Property IsAlive() As Boolean' for interface 'Common.IServerConnector'. Implementing property must have matching 'ReadOnly' or 'WriteOnly' specifiers. D:\My Documents\Visual Studio 2005\Projects\CHANGEIP\apps\hb3svc\ServerConnector.vb 7 16 hb3svc

 
At 1:39 AM, December 24, 2005, Blogger arun chaudhary said...

I have also converted all c# to vb.net but facing some problem. how i can upload my code to you

 
At 5:19 AM, December 25, 2005, Blogger j1hammer said...

You are more then welcome to email me: j1hammer@hotmail.com.

 
At 11:02 AM, December 29, 2005, Anonymous Anonymous said...

When I convert to using a IPC channel the broadcaster no longer is subscribed to for some reason ... other objects that I have are still remoted but the broadcaster does not work. Are there some permissions or problems with using IPC and this model you have designed?

config file channel attributes:

ref="ipc"
portName="localhost:65530"
authorizedGroup="Everyone"

Thanks for the excellent work!

 
At 11:03 AM, December 30, 2005, Blogger j1hammer said...

Because of public demand, now a full working source code in VB.NET:

http://www.hmr.co.il/Yariv/ServerDisconnections_VB.zip

 
At 10:47 PM, December 30, 2005, Anonymous Anonymous said...

You rock!

I pretty much had it converted but wasted many hours troubleshooting the IPC connection. I've got it running under 2.0 using IPC channels - but it seems as though the events are not being fired for some reason. Change the config back to TCP and it works perfect. I'll post a solution if i find one.

Thanks again, you saved me tons of work and taugh me a lot with your article.

 
At 1:06 PM, January 16, 2006, Blogger P Durrant said...

can a second channel be add so this can work across a WAN aswell

 
At 5:19 PM, February 18, 2006, Anonymous Anonymous said...

I spent about 2 weeks messing with this code and trying to get it to work with IPC channels ... I guess there is no way to use IPC and receive server events for some reason. If anyone can get this to work with IPC i'd be ecstatic.

 
At 12:51 AM, December 06, 2006, Anonymous Anonymous said...

Yes same here tried getting events using IPC without success! Any ideas?

 
At 5:41 AM, September 23, 2008, Anonymous Anonymous said...

Hi,
I would like to know what happens when server tries to send a message back to client but the client is unreachable for some reasons ( network drop but client application is still running and network is back on later time).

Would you remove the client from the event notification list or you keep it until the client actually remove it.

If we remove the unreachable client from the list that would create a problem for the client because client has no knowledge of what happen to his subscription and client never receive any event from server for a particular subscription.

If we do not remove unreachable client then it creates different problems for server for keeping event list for no client, and ...etc.



Thanks a lot for you comments

 

Post a Comment

<< Home

Feel free to use everything here. Add links to my site if you wish.

Do not copy anything to other sites without adding link to here.

All the contents of the site belong to Yariv Hammer.