Google
WWW Yariv Hammer's Code Site

Saturday, November 12, 2005

Creating a Broadcast Events Server Using .NET Remoting - A Step-By-Step Tutorial

Introduction
It is possible to use events in Remoting. This means that the server can raise events in the clients, which means that client don't need to use timers in order to check if something happened in the server. This is a very important capability of .NET Remoting.
However, it is not such an easy job using remote events, as you would imagine. This is a tutorial which will help you create your events. Simply follow it step by step, and customise it for your needs.
The demo is in C#.
Download Full Source Code (<100kb)

Step 1 - Setup the solution
Create an empty solution. Add three projects to it:


  • Server - A windows application.
  • Client - A Windows application.
  • Common - A Class library
Add a reference from the Client and the Server to the Common library.
Remove all forms and classes autogenerated for you when you set up the new projects.

Step 2 - Create a Message Class and IBroadcaster interface
In Common library, add a new class called Message.
Here is the code:
-----------------------------------

using System;
namespace Common
{
[Serializable()]
public class Message
{
public string _msg;
public Message(string msg)
{
this._msg = msg;
}


public string Text
{
get { return _msg; }
}

}
}

--------------------------------------------
Note the Serializable attribute. The Message is going to cross processes between the clients and the server.

Next add a new class to Common. called IBroadcater. We will change it an interface:
--------------------------------------------
namespace Common
{
public delegate void MessageArrivedHandler(Message msg);

public interface IBroadcaster
{
void BroadcastMessage(Message msg); // Send message
event MessageArrivedHandler MessageArrived; // Receive message
}
}

--------------------------------------------
We have just defined a delegate to use as our event, and n interface which will handle both sending the message and receiving it.
Notice that I put an interface in Common. This way the client will only be able to access the broadcaster using the interface, without having access to the actual implementaion.

Step 3 - Create the Broadcaster Class
Now add a class to the Server project. Call it Broadcaster.
Here is the code:
-------------------------------------------

using System;
using Common;
namespace Server
{
[Serializable()]
public class Broadcaster:MarshalByRefObject,IBroadcaster
{

public override InitializeLifetimeService()
{//Add this to the download source code.
return null;
}
public event MessageArrivedHandler MessageArrived;
public void BroadcastMessage(Message msg)
{
SafeInvokeEvent(msg);
}
private void SafeInvokeEvent(Message msg)
{
if (MessageArrived == null)
return;
MessageArrivedHandler mah=null;
foreach(Delegate del in MessageArrived.GetInvocationList())
{
try
{
mah = (MessageArrivedHandler)del;
mah(msg);
}
catch (Exception ex)
{
Console.Write(ex.Message);
MessageArrived -= mah;
}
}
}
}
}

----------------------------------------------------
First of all, credits for this class must go to Ingo Rammar's book "Advanced .NET Remoting".
This class is very important to understand. Notice that the class derives from MarshalByRefObject. This means we are going to register it for remoting - when clients will call it, they will use a proxy, and the actual call will be done in the Server process. Next notice that we implement the IBroadcaster interface. This will alow it to access the class from the clients (The inetrface resides in the Common interface which is refernced by the client).
The main thing to notice is the SafeInvokeEvent method. Intead of just raising the event like I would normally do, I iterate through all the delegates registered to the event (these are all remote events!!), and invoke them one by one. If for any reason the invoke is failed, I remove the delegate rom the InvokationList. This is super important. Without it when a client will disconnect I will not be able to raise the event again!!!

Step 4 - Setup Remoting in the Server
Add a new item to the Server - An Application Configuration File (App.Config)
-----------------------------------------------


<configuration>
<system.runtime.remoting>
<application name="EventServer">
<service>
<wellknown mode="Singleton" type="Server.Broadcaster, Server" objectUri="Broadcaster.soap"/>
</service>
<channels>
<channel ref="tcp" port="16784">
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full"/>
</serverProviders>
<clientProviders>
<formatter ref="binary"/>
</clientProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>

----------------------------------------------------------
The name of server is configurable (remember it for the client's configuration file). Same for the port, and the ObjectURI.
The type must contain the full name of the object (containing namespace), and the server assembly name.

Add a Form to the Server called ServerForm. No code needed yet.
Add a class to the server called ServerStartup:
----------------------------------------------------------

using System.Windows.Forms;
namespace Server
{
public class ServerStartup
{
[STAThread]
static void Main()
{
bool createdNew;
Mutex m = new Mutex(true, "EventServer", out createdNew);
if (!createdNew)
{
return;
}
InitRemoting();
Application.Run(new ServerForm());
GC.KeepAlive(m);
}
private static void InitRemoting()
{
string fileName = "server.exe.config";
RemotingConfiguration.Configure(fileName);
}
}
}

-------------------------------------------------------
Few things to notice here: The Main method forces this application to be single instance (Check this post). The InitRemoting method reads the App.Config file and setup the Broadcaster class for remoting use.

Step 5- Creating EventHelper
Add a class to Common called BroadcastEventHelper:
-------------------------------------------------------

using System;
namespace Common
{
public interface IEventHelper
{
event MessageArrivedHandler MessageArrivedLocally;
void LocallyHandleMessageArrived(Message msg);
}
[Serializable()]
public class BroadcastEventHelper:MarshalByRefObject,IEventHelper
{
public event MessageArrivedHandler MessageArrivedLocally;
public void LocallyHandleMessageArrived(Message msg)
{
if (MessageArrivedLocally != null)
MessageArrivedLocally(msg);
}
public override object InitializeLifetimeService()
{
return null;
}
}
}

-------------------------------------------------------------------
First we define IEventHelper interface, which has an event and a method to raise the event.
The class of BroadcastEventHelper is going to be used in the client process (not the server) in a way I will show and explain in the next step. It may seem unnessecary to you now, but actually it is very important. It implements the IEventHelper interface as well as inherits from MarshalByRefObject.

Step 6 - Creating MessageBroadcaster in the Client
First we need to add a class to Common, called RemotingHelper:
-------------------------------------------------------------------

using System;
using System.Collections;
using System.Runtime.Remoting;
namespace Common
{
public class RemotingHelper
{
internal static bool _isInit = false;
private static IDictionary _wellKnownTypes;
public static object GetObject(Type type)
{
if (!_isInit)
InitTypeCache();
WellKnownClientTypeEntry entr = (WellKnownClientTypeEntry) _wellKnownTypes[type];
if (entr==null)
throw new RemotingException("Type not found");
return Activator.GetObject(entr.ObjectType,entr.ObjectUrl);
}
private static void InitTypeCache()
{
_wellKnownTypes = new Hashtable();
foreach(WellKnownClientTypeEntry entr in RemotingConfiguration. GetRegisteredWellKnownClientTypes())
_wellKnownTypes.Add(entr.ObjectType,entr);
_isInit = true;
}
}
}

------------------------------------------------------
This class will help us to get the Singleton instances of the Remote objects generically. (For further details check this post).

We will add a special Exception called ServerNotAliveException, so we add a file called Exceptions to common, and add this code:
------------------------------------------------------

using System;
namespace Common
{
public class ServerNotAliveException:Exception
{
public override string Message
{
get
{
return "Server is not connected!";
}
}
}
}

-------------------------------------------------------

Now we add a class to the Client project called MessageBroadcaster:
-------------------------------------------------------

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()
{

InitializeObjects();
}
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);
}
public event MessageArrivedHandler MessageArrived;
}
}

-----------------------------------------------------
_bcaster is a member of typed IBroadcaster, but actually it is the Broadcaster object in the server. _eventHelper is the BroadcastEventHelper from the previous step, and is in the client process. We use _bcaster in order to Broadcast an event (sending it to all the connecting clients) by calling BroadcastMessage. We sign up the MessageArrived event of the server object with the delegate from BroadcastEventHelper and we sign up for the MessageArrivedLocally. Here is what's going on: If a client raises an event it calls BroadcastMessage in the Server Broadcaster. The server uses the SafeInvokeEvent method and raises the event in all the clients. The client receives the event in the BroadcasterEventHelper, and the method LocallyHandleMessageArrived is called. The method raises another event, MessageArrivedLocally , which is now in the client process entirely. The MessageBroadcaster method HandleMethod is then called and raises the event MessageArrived. Until now everything was generic, and the client will use the MessageBroadcaster objects, and listen to this event, without being concerned with this entire process.
Why do we need all that complexity? When the client raises an event in the server they actually swap roles: The Server becomes the client of the event raised by the Client (Which becomes the server). In order for that to work the server must have a reference to the MarshalByRefObject in the client that raised the event. This cannot be: The server cannot have a reference to client assembly, and we can also have a circular referencing problem. That is why we have the BroadcastEventHelper. This class does nothing beside handle the events (no specific logic or data), and it resides in the server process. The client and the server reference both to the Common assembly so our problem is solved. The only minus is that we need to have another wrapper for the event in the client process.

Step 7 - Setup Remoting in the Client
Our final step will be to add an App.Config file to the Client:
------------------------------------------------------------------
<?xml version="1.0" encoding="utf-8" ?>
<configuration>
<system.runtime.remoting>
<application name="EventClient.exe">
<client>
<wellknown type="Common.IBroadcaster, Common" url="tcp://localhost:16784/EventServer/ Broadcaster.soap"/>
</client>
<channels>
<channel ref="tcp" port="0">
<serverProviders>
<formatter ref="binary" typeFilterLevel="Full"/>
</serverProviders>
<clientProviders>
<formatter ref="binary"/>
</clientProviders>
</channel>
</channels>
</application>
</system.runtime.remoting>
</configuration>

------------------------------------------------------------------
We register the IBroadcaster interface for remoting (instead of the concrete class) and the URI tells what class we mean. You can replace the IP address, port, and name of the server application (look at the server configuration file).

Now we add another Form called ClientForm.
We add a class called ClientStartup to the Client:
------------------------------------------------------------------

using System;
using System.Runtime.Remoting;
using System.Windows.Forms;
namespace Client
{
public class ClientStartup
{
[STAThread]
static void Main()
{
InitRemoting();
Application.Run(new ClientForm());
}
private static void InitRemoting()
{
string fileName = "client.exe.config";
RemotingConfiguration.Configure(fileName);
}
}
}

------------------------------------------------------------------

Now a demo to see how it works.
Demo
In the ClientForm drag a ListBox, and call it lsbMessages, and a Button called btnSendMessage with text "Send Message". Double-click on the Button, and add this code to the form:
-----------------------------------------------------------------

using System.Threading;
using Common;

namespace Client
{
public class ClientForm : System.Windows.Forms.Form
{
(...)

public ClientForm()
{
InitializeComponent();
_messageBroadcaster.MessageArrived += new Common.MessageArrivedHandler(broadcaster_MessageArrived);
}
(...)

MessageBroadcaster _messageBroadcaster = new MessageBroadcaster();
private long _index=0;
private void btnSendMessage_Click(object sender, System.EventArgs e)
{
Thread t = new Thread(new ThreadStart(SendMessage));
t.Start();
}
private void SendMessage()
{
try
{
Message msg = new Message("This is message #" + (_index++).ToString());
_messageBroadcaster.BroadcastMessage(msg);
msg = null;
}
catch (ServerNotAliveException ex)
{
lsbMessages.Items.Add(ex.Message);
lsbMessages.SelectedIndex = lsbMessages.Items.Count-1;
}
}
private void broadcaster_MessageArrived(Message msg)
{
lsbMessages.Items.Add(msg.Text);
lsbMessages.SelectedIndex = lsbMessages.Items.Count-1;
}
}
}

-----------------------------------------------------------------
Note how we handle the MessageArrived event (by adding the message to the ListBox). The reason I used a Thread to croadcast the event, was in order not to block the UI when calling the server. Keep in mind this is just an example.

You can now run the server, and as many clients as you want, press the buttons and see how all the clients receive the event.

Lets improve the Server:
Add the class RemoteSingletonObjectsList to the server
-----------------------------------------------------------------
using System;
using System.Collections;
namespace Server
{
public class RemoteSingletonObjectsList
{
private static RemoteSingletonObjectsList _instance = new RemoteSingletonObjectsList();
public static RemoteSingletonObjectsList Instance
{
get
{
return _instance;
}
}
private RemoteSingletonObjectsList()
{
}
private Hashtable _objects = new Hashtable();
public void RegisterRemoteSingletonObject(object obj)
{
_objects[obj.GetType()] = obj;
}
public object GetRemoteSingletonObject(Type type)
{
return _objects[type];
}
}
}

------------------------------------------------------
This class will keep the singleton instances of the remote objects, thatway we access the same instances the client see from the server process.
Go to ServerStartup class and change a bit the InitRemoting method:
-----------------------------------------------------

private static void InitRemoting()
{
string fileName = "server.exe.config";
RemotingConfiguration.Configure(fileName);
foreach (WellKnownServiceTypeEntry entry in RemotingConfiguration. GetRegisteredWellKnownServiceTypes())
{
MarshalByRefObject pxy = (MarshalByRefObject) Activator.GetObject(entry.ObjectType, "tcp://localhost:16784/EventServer/" + entry.ObjectUri);
pxy.CreateObjRef(entry.ObjectType);
RemoteSingletonObjectsList.Instance. RegisterRemoteSingletonObject(pxy);
}
}

-----------------------------------------------------------
Now add to the ServerForm a ListBox (lsbMessages) and a button (btnSend with text "Send Message"). Double click on the button and add the code:
-----------------------------------------------------------

using System.Threading;
using Common;
using Message = Common.Message;

namespace Server
{
public class ServerForm : Form
{
(...)

public ServerForm()
{
InitializeComponent();
_bcast = (Broadcaster) RemoteSingletonObjectsList.Instance. GetRemoteSingletonObject(typeof(Broadcaster));
_bcast.MessageArrived += new MessageArrivedHandler(bcast_MessageArrived);
}
(...)

private Broadcaster _bcast = null;
public void bcast_MessageArrived(Message msg)
{
_msgToShow = msg;
Thread t = new Thread(new ThreadStart(ShowMessage));
t.Start();
}
private Message _msgToShow=null;
private void ShowMessage()
{
if (_msgToShow == null) return;
lsbMessages.Items.Add(_msgToShow.Text);
lsbMessages.SelectedIndex = lsbMessages.Items.Count -1;
_msgToShow = null;
}
private long index=0;
private void btnSend_Click(object sender, System.EventArgs e)
{
_bcast.BroadcastMessage(new Message("Server: This is message #" + (index++).ToString()) );
}

}
}

-----------------------------------------------------------
Now run the server and a few clients and press on buttons in the clients and server and see the events coming.

In the next post I will show how you can notify the clients and the server when there is a disconnection, and let them recover such disconnections.

21 Comments:

At 8:52 AM, November 18, 2005, Anonymous Anonymous said...

This is brilliant!

Please make me a baby

Continue to publish here stuff

 
At 8:48 PM, January 16, 2006, Anonymous sailee said...

Hi,
I was trying to create a chat application, and found your tutorial very helpful.
Please continue to great work!!
I had a difficulty though.
I tried running the same code over two different machines on the network, and it did not work...why is that happening?
if possible please mail me at sailee_latkar@yahoo.co.in

 
At 6:46 AM, January 20, 2006, Anonymous Anonymous said...

Hi,

Did you replace the
"tcp://localhost:16784/EventServer/Broadcaster.soap" setting in the confifuration files? that might fix it

Endre

 
At 2:44 AM, January 21, 2006, Blogger j1hammer said...

Hi Sailee,
Change all the references to "localhost" both in configuration files and in code (I think it appears in code in one place - It's best to put it also in configuration file) to the ip address of the server computer.

 
At 1:35 PM, November 20, 2006, Anonymous Anonymous said...

I am new to .NET and am getting cross-threading errors at lsbMessages.Items.Add(_msgToShow.Text.

I am using VS2005.

 
At 11:08 AM, February 20, 2007, Blogger Rachel said...

hello,
I downloaded the sample and it works great.

Now, when I tried putting common.dll in GAC (gave it sn and used gacutil /i to put it in GAC), things stopped working because entry.ObjectType was set to null (in InitRemoting() at the Main class if the server).
Any idea why ?

 
At 5:22 AM, February 26, 2007, Blogger j1hammer said...

Quote: "Anonymous said...
I am new to .NET and am getting cross-threading errors at lsbMessages.Items.Add(_msgToShow.Text.

I am using VS2005. "

Well, in 2005 they are more strict regarding multithreading - you cannot access the GUI using other threads. You will need to use Invoke in order to call methods of the ListBox.
Instead, try to set the Form.CheckForIllegalCrossThreadCalls property to false.

 
At 2:27 AM, December 20, 2007, Blogger Nachi said...

Yariv Hi,
Great article
I is possible to get in in VB.net
thank
Nachi

 
At 2:29 AM, December 20, 2007, Blogger Nachi said...

Where can I find VB.Net src for this article?

Thanks

 
At 12:10 AM, March 14, 2008, Blogger i luk lik rahul bakshi said...

hello, can someone please assist me in developing an Instant messenger that can send private messages as well. I have developed an application that works just like a chat room. users send messages that are readable by all the users. i want to extend this application to send private messages so that one user can send message to a particular user.

 
At 6:10 AM, May 27, 2008, Blogger CB Mehta said...

This comment has been removed by the author.

 
At 6:18 AM, May 27, 2008, Blogger CB Mehta said...

Hi, This is really good. Thanks

Issue : I m using this to log user info(a,b,c) into database.
To do this I receive the info from user, log into database, retrieve latest log report from database in dataset and then broadcast it to all connected users.
when i receive this dataset at user end, I bind it with datagridview to populate the data.
First time it binds succesfully but from next time , it generates error and close the application.

Pl help and reach me at cbmehta23@gmail.com

 
At 11:17 PM, May 30, 2008, Blogger CB Mehta said...

Event Listner not working in Client App

Hi, I m expanding ur code to develop trading application to accept bid/ask value from user and broadcast to every connected user.
Process is working smoothly on localhost as well as on LAN.
But it is not working when i host app on static IP based server.Client send bid/ask value to server but does not get updates except client running on server.

Currently i m running server app manually by clicking on server.exe.
I did not get any change if I replace http channel with tcp
Any comment/suggestions r invited. Thanks .

 
At 12:55 PM, June 05, 2008, Blogger Malcolm said...

Hi Yariv

you are my Jesus, I have been struggle this remote event issu 2 days, and can not make it work until to find your post.

you are such young handsome expert.

thank you very much
-Malcolm

 
At 8:08 AM, August 06, 2008, Blogger Luca said...

Hi,
very good article.!
I´ve "traduced" all your code in VC++ 2005, but when I run the applications I get an exception in InitTypeCache()
in _wellKnownTypes->Add(entr->ObjectType,entr);

Infact the entr->ObjectType is nullptr . I belive there is something wrong in my client.exe.config, what?
I ´ve copied that file from your blog.
if possible please mail me at
depal_luca@libero.it

Thanks in advance

Luca

 
At 12:10 PM, August 20, 2009, Anonymous Anonymous said...

I use a template of this code and it worked fine when I had a very simple message as the author indicates. Then I changed the message to contain a list of messages (see below). And I started getting:

Server stack trace:
at System.Runtime.Remoting.Channels.Ipc.IpcPort.Write(Byte[] data, Int32 offset, Int32 size)
at System.Runtime.Remoting.Channels.Ipc.PipeStream.Write(Byte[] buffer, Int32 offset, Int32 count)
at System.Runtime.Remoting.Channels.ChunkedMemoryStream.WriteTo(Stream stream)
at System.Runtime.Remoting.Channels.StreamHelper.CopyStream(Stream source, Stream target)
at System.Runtime.Remoting.Channels.Ipc.IpcClientHandler.SendRequest(IMessage msg, ITransportHeaders headers, Stream contentStream)
at System.Runtime.Remoting.Channels.Ipc.IpcClientTransportSink.ProcessMessage(IMessage msg, ITransportHeaders requestHeaders, Stream requestStream, ITransportHeaders& responseHeaders, Stream& responseStream)
at System.Runtime.Remoting.Channels.BinaryClientFormatterSink.SyncProcessMessage(IMessage msg)

Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at Remoting.Common.BroadcastEventHelper.LocallyHandleMessageArrived(IRTDMessage msg)
at Remoting.Server.Broadcaster..SafeInvokeEvent(IMyCustomMessage msg)

Any ideas? The only thing I changed was that I replaced the Message you provide with a message containing a list of my messages:

public class CompositeMessage() : IMyCustomMessage {
public IList<IMyCustomMessage> itsMessages = new List<IMyCustomMessage>();
}

 
At 12:16 PM, August 20, 2009, Anonymous Anonymous said...

Realized this post is somewhat but in case someone is reading it. The previos message should include the correct stack trace (had to modify a little before posting):

System.Runtime.Remoting.RemotingException: Failed to write to an IPC Port: The pipe is being closed.


Server stack trace:
at System.Runtime.Remoting.Channels.Ipc.IpcPort.Write(Byte[] data, Int32 offset, Int32 size)
at System.Runtime.Remoting.Channels.Ipc.PipeStream.Write(Byte[] buffer, Int32 offset, Int32 count)
at System.Runtime.Remoting.Channels.ChunkedMemoryStream.WriteTo(Stream stream)
at System.Runtime.Remoting.Channels.StreamHelper.CopyStream(Stream source, Stream target)
at System.Runtime.Remoting.Channels.Ipc.IpcClientHandler.SendRequest(IMessage msg, ITransportHeaders headers, Stream contentStream)
at System.Runtime.Remoting.Channels.Ipc.IpcClientTransportSink.ProcessMessage(IMessage msg, ITransportHeaders requestHeaders, Stream requestStream, ITransportHeaders& responseHeaders, Stream& responseStream)
at System.Runtime.Remoting.Channels.BinaryClientFormatterSink.SyncProcessMessage(IMessage msg)

Exception rethrown at [0]:
at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
at Remoting.Common.BroadcastEventHelper.LocallyHandleMessageArrived(IMyCustomMessage msg)
at Remoting.Server.Broadcaster.SafeInvokeEvent(IMyCustomMessage msg)

If you can tell me what I am doing wrong it would be greatly appreciated.

 
At 5:47 AM, September 28, 2009, Blogger Lloyd Saldanha said...

This comment has been removed by the author.

 
At 6:36 AM, September 28, 2009, Blogger Lloyd Saldanha said...

This comment has been removed by the author.

 
At 10:06 PM, November 21, 2009, Anonymous Anonymous said...

Who knows where to download XRumer 5.0 Palladium?
Help, please. All recommend this program to effectively advertise on the Internet, this is the best program!

 
At 2:26 AM, February 08, 2010, Anonymous Anonymous said...

Great article as for me. It would be great to read more about that theme. The only thing it would also be great to see here is some pictures of some devices.
Kate Stepman
Block phone

 

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.