Click here to Skip to main content
15,881,852 members
Articles / Programming Languages / C#
Article

Publish And Subscribe with C#

Rate me:
Please Sign up or sign in to vote.
4.00/5 (10 votes)
2 Apr 200214 min read 161.8K   2K   69   13
This tutorial will introduce the concepts of publishing and subscribing data over a network using C#

Introduction

This tutorial will introduce the concepts of publishing and subscribing data over a network using the C# programming language and will hopefully provide not only an understanding of how publish and subscribe applications work but should provide the reader with the tools needed to develop a publish and subscribe style application of their own. The demo code is split into five separate projects. To start with we will look at the mechanics of how the project works and then move on to the details of each of the projects components.

The projects were developed with Microsoft Development Environment version 7 on Windows XP.

Image 1

Image 2

Timers

The project is actually run through the use of timers that at set intervals will in the case of the Publisher project the timer fires an instance that will publish anything that has registered an interest in subscribing to the information. It should be noted that in a real application there would be some kind of identifier so that the Publisher would only send certain information to certain subscribers. So at the moment the publisher will send all the subscribed information to all the subscribers. The subscriber/s have a timer that once fired redraws all the subscribed items that it has information on and then waits for the timer to fire again. At this point I am deliberately avoiding how the information gets from the publisher to the subscriber but we will get to that, first we need to see how to set up the timers correctly.

In order to set up a timer you declare it like any other C# object,

C#
private Timer timer;

timer = new Timer();
/// set up the timer event handler
timer.Tick += new EventHandler( TimerEvent );
/// set the interval
timer.Interval = 5000;
/// start the timer
timer.Start();

The Timer is declared as a private member of the class which is then instantiated during the setting up of the project ( The above code is from the Publish project. ) The Timer has an Interval value that is set at the number of milliseconds between the timer firing and a Tick function that is an

EventHandler
object that indicates the function that is to be called when the timer is fired. Once these have been set the timer start can be called and the function will be called whenever the timer fires.

An EventHandler is a special function type that is called whenever an event is fired be it through the use of a timer or as we will see later a callback function. In the case of the Timer class the Tick member is an event that takes a an EventHandler function to call when the Tick event is fired every

Timer.Interval
milliseconds. The function for the event handler is delegated to the function defined with the following properties,

C#
private void TimerEvent( object PassedObject, EventArgs TimerArgs )

In the function declaration above both the class specification of private and the name of the function are up to the developer but the function must have a return type of void and take the two parameters defined above. The object variable PassedObject is the sender or the source of the event, because this is used to implement the calling of a timer function this will be the timer object that is defined in the class although to use it to say change the frequency of the call you would be required to cast it back to the type of a Timer. e.g.

C#
Timer tempTimer = ( Timer )PassedObject;
tempTimer.Interval = ( tempTimer.Interval - 1000 );

The EventArgs parameter is the event argument. That is, it is the object that the event is fired for. The EventArgs class is stateless which means that by itself it will hold no data. If we wish to pass data to an EventHandler function, a class must be created that inherits from EventArgs. This is exactly what will be happening with the FakeTicker class later. But for now that is how the timer works first by declaring a Timer object and a function that follows the EventHandler definition of having a void return type and takes an object as its first parameter and a EventArgs object as its second parameter. All that is needed then is to set up the function so that it is called when the event is fired by declaring a new EventHandler for the Timer.Tick event and passing the name of the declared function as a parameter.

Crossing Boundaries

Having gotten an idea of events we now want to to do something a bit more interesting than fire them within projects. That's all well and good but what if we could use these things to get programs to talk to each other? I'm sure it would be even better if we could do it in such a way that the programs could be running on different computers at the same time, but what exactly would that involve? The answer is basically Channels. Get to love them as from now on they'll be part of every Windows release.

Crossing The Channel

The sample code uses two TcpChannel objects, both of which are used in the Windows Form classes. One is used to advertise the Publisher object and one is used to get the Publisher object. This means that if the code is run on two computers the Publish object can be fired up on one and the when the client is ready it can get the Publisher object. As long as the Publish application is running and the client knows where to find it there will be no problems.

Naturally when publishing an object on one computer an receiving it on another the code is going to be slightly different. So let's look at Publishing an object first.

C#
private TcpChannel Channel; 
/// create the channel on port 8090 
Channel = new TcpChannel( 8090 ); 
/// register the channelwith the runtime
ChannelServices.RegisterChannel( Channel ); 
/// register the remote type and pass in a uri identifying string 
RemotingConfiguration.RegisterWellKnownServiceType( typeof( Publisher ), 
                      "PublishSubscribe", WellKnownObjectMode.Singleton );       

To start with the TcpChannel is created as a private member of the class and when it is constructed using new. the constructor is passed the value of 8090 which is the Tcp Channel that the code will make the Publisher object available on. Once the TcpChannel is created it needs to be registered with the .NET runtime ChannelServices using the ChannelServices.RegisterChannel which will then allow the channel to be broadcast or to receive or even do both at the same time. Then all that has to be done is to either make the class object available to other computers or processes on the same machine. This is done using the RemotingConfiguration class that allows the registration of the object using either of the RegisterWellKnownType functions which come in two flavors the Service and the Client. To publish an object, the

RegisterWellKnownServiceType
is used. This takes the type of the object to be published, in this case the Publisher object, the Uri name of the published object and the publication type. The publication type can be either the WellKnownObjectMode.Singleton or the WellKnownObjectMode.SingleCall. The
WellKnownObjectMode
is an enumeration that contains only these two options, the Singleton means that every client that accesses the Publisher object will receive the same Publisher object and the SingleCall option means that for every client that calls into the service a new object of type Publisher will be created. For this project the Publisher Object is published as a Singleton as the Publish project that broadcasts the Publisher object later gets the object that it publishes itself so that it can call into the Publisher object when the timer is fired. Admittedly this is a fudge to make the project work and in the real world you would expect more than one object to be involved in the publication of data and the receiving of the information to be published.

Once the object is published there needs to be a mechanism to use it. Ignoring for a moment that the publishing project subscribes to the object it publishes, lets look at the client code in the Subscribe project.

C#
ClientChannel = new TcpChannel( 0 );

/// register the acceptance of the required channel
ChannelServices.RegisterChannel( ClientChannel );

/// get the serverobject from the required channel
try
{
    NewPublisher = ( Publisher )Activator.GetObject( typeof( Publisher ), 
                                   "tcp://localhost:8090/PublishSubscribe" ); 
}
catch( NullReferenceException nullExp )
{
    MessageBox.Show( "The url for the object is invalid " 
                    + nullExp.Message );
}
catch( RemotingException remExp )
{
    MessageBox.Show( "The object type is not defined properly, " +
                     "it needs to be derived for a remoting class " + 
                     remExp.Message );
}        

Aws with publishing an object it is necessary to first of all create a

TcpChannel
object that can be used to receive information. The important thing to note about the objects creation here is that the channel is created on a port of 0 because if at this point you try to register the Channel on the port 8090 the application will throw an exception that says that the channel is already in use. Using 0 allows the client to listen on any channel the actual channel that the client uses is passed when we get the object from the publisher.

C#
"tcp://localhost:8090/PublishSubscribe"

Before we can do this though we need to register the client channel in exactly the same way that the publisher is registered by calling

C#
ChannelServices.RegisterChannel( ClientChannel );

Once the client is registered the publisher object is obtained by using the Activator.GetObject function.

C#
NewPublisher = ( Publisher )Activator.GetObject( typeof( Publisher ), 
           "tcp://localhost:8090/PublishSubscribe" );

The Activator class is effectively a helper class for creating objects remotely. The GetObject function returns an object that is of the type specified by the first parameter and the second parameter is the url of the object in this case the full url for the object is "tcp://localhost:8090/PublishSubscribe" where "tcp" is the channel protocol, "localhost" is the current machine address this can be Localhost as it is here or an actual address such as 192.2.2.2. The "PublishSubscribe" is the name of the project that the publisher is publishing on. Finally the object that is returned is cast back to its object type and the Publisher object NewPublisher is made equal to it. Well its made equal to the proxy of the object and not the actual object itself but as far as the NewPublisher object is concerned it is all the same.

Publishing And Subscribing

A publish and subscribe set up is based around two functions.

C#
///
/// declare the definition for the function 
/// 
public delegate void PublishedTickerFunction( object SourceOfEvent, 
          FakeTicker FakeTickerArg ); 
    
///
/// declare the event to be triggered when a fake  ticker is updated 
/// 
public event PublishedTickerFunction OnUpdatedFakeTicker;

There are two keywords to take notice of here the first is a delegate and the second is the event keyword. A delegate is a template to a function. By declaring something as a delegate you are telling the compiler that at some point you are going to be using a function with this signature. This is used in much the same way as you would use the timer only in this case the delegate function that will be used will be in a separate project, across process boundaries and even across computers. As with the timer two parameters are declared for the function the first is the object that represents the source of the event . In this case whenever a function of the PublishedTickerFunction type is called the object parameter SourceOfEvent is equal to the Singleton Publisher object declared when the Publisher is published. If the call to

RemotingConfiguration.RegisterWellKnownServiceType
had taken the
WellKnownObjectMode.SingleCall
as its third parameter then the SourceOfEvent object would be equal to the individual Publisher object that would have been created whenever a client made a connection to the object being broadcast. The second parameter for the function is of the FakeTicker type this is an object that is derived from the EventArgs class that is used in the timer. Being a delegate the PublishedTickerFunction is not called in the way that you would expect if you are new to the concept of delegates. The function is not called this way

C#
Publisher.PublishedTickerFunction( object SourceOfEvent, 
                                   FakeTicker FakeTickerArg );        

It is however called this way.

C#
NewPublisher.OnUpdatedFakeTicker += 
   new Publisher.PublishedTickerFunction( NewSubscriber.OnSubscriberUpdate );

The code line above from the Subscribe project shows that the Singleton

NewPublisher
object adds the the required function to be called back from the publisher to its event definition in the same way that the timer adds an event handler to its timer.Tick EventHandler. What is happening here is exactly the same as with the timer only it will go across process boundaries. The function to be called back when and an event is triggered is called naturally enough a callback function and this is declared as,

C#
public void OnSubscriberUpdate( object SourceOfEvent, 
                                FakeTicker fakeTickerArg )

It can be seen from the above line that the function above declared in the Subscriber class takes exactly the same parameters that the

PublishedTickerFunction
specifies but the call to the PublishedTickerFunction which you will notice is called as though it were a static function, takes as its parameters not the arguments it specifies but a function that itself has the same declaration signature as declared by the PublishedTickerFunction. Which in practice means that whenever the code,

C#
foreach( FakeTicker fake in FakeTickerList ) 
{ 
    /// give it a new value 
    fake.Calculate(); 
    /// publish it 
    OnUpdatedFakeTicker( this, fake ); 
}

is executed all the clients that have registered with the Publisher application will be notified of the updates to each FakeTicker in the FakeTickerList. Which means that every time that the loop is run not just one but all clients will be called as the OnUpdatedFakeTicker is an event handler list and not a single call as it is with the time. Though seeing as the timer.Tick function uses the same += way of adding the function that is called by the timer it is theoretically possible at least to have more than one function being called whenever the timer event is triggered.

The Components

FakeTicker

The FakeTicker project is a dll that is referenced by all the other projects. The reason for this is that the file could either be included in every project which would lead to circular references where the compiler would never be able to release any resources allocated to the FakeTicker class or only included in some of the projects which wouldn't have the desired result for the program. The FakeTicker class is declared as,

C#
[Serializable] 
public class FakeTicker : EventArgs 
{

The reason for this is that to be included in the event firing function the

FakeTicker
class must be derived from the EventArgs class. This is a class which has the Serializable attribute which means it can be passed across the wire, which is just a away of saying that it can be passed between processes wherever those processes are running. This in any case is what we are using the Serializable attribute for in this project, though it can be used for writing the object to any medium that can receive it.

Publish

The Publish project contains the PublishService class that controls the Publish form. The class sets up a timer that fires every five seconds and publishes any events that the clients have subscribed to. This class publishes the Publisher object as a Singleton as described above and then subscribes to it itself using,

C#
NewPublisher = ( Publisher )Activator.GetObject( typeof( Publisher ), 
     "tcp://localhost:8090/PublishSubscribe" );

Publisher

The Publisher Project is the main part of the application that contains the declarations of the delegate and the event functions that drive the application.  This class also contains the PublishNewEvents function that controls calls the OnUpdatedTicker function which ensures that all the subscribed clients are called back with the latest data. This class inherits from MarshalByRefObject which is a framework class that will set up the proxies or code that will allow the process boundaries to be crossed while as far as the local code is concerned there are no process boundaries being crossed.

Subscribe

The Subscribe project contains the SubscibeClient class that runs the Subscribe form and sets up the callback function with the publisher so that the

OnSubscriberUpdate
function will be called whenever the publisher fires an event. The SubscribeClient class subscribes to the published Publisher object and sets up a timer that runs every three seconds to check if the data has been updated. This is set to a shorter time than the class that publishes the data so that the clients always have the most relevant data but it is set at not too short a time so that if you have more than one Subscribe project running you can see the clients update at slightly different times.

Subscriber

The Subscriber project contains the Subscriber class that also inherits from the

MarshalByRefObject
class that allows the callbacks that are set up in the code to be called when a new event is published. The class is quite simple in that it mainly contains the callback function that is triggered from the publisher. All this class does once the function is called is save the data that the function receives so that the Subscribe form can display it when it's own timer is fired.

Running The Project

To run the project press F5 and the Publish application will start in debug mode. Then right click on the Subscribe Project go to Debug and left click on start new instance. Then type in a name and give it a starting value. No attempt is made for numeric accuracy so it doesn't really matter what number you set. Note there is no set limit on how many Subscribe projects you can run.

Conclusion

With any luck this will enlighten more people than it confuses but as with many things in programming sometimes you just don't get them until you try them so try it. Remoting with C# can open up whole new ways of programming with server banks containing ready to use classes that only have to be picked up. Of course this probably wont happen like that as the requirements will always change slightly from project to project but think about it for a minute using this technology you can run a security database from a separate server that checks people's security access in realtime by simply getting a security object from the server and questioning it or if you're running from a large firm where a lot of things are standardised. you can programmatically setup computers just by running a small program that gets everything from the server. And the most important thing of all is that once you understand the concepts writing the code is really easy.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here


Written By
Web Developer
United Kingdom United Kingdom
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions

 
Generaltwo issues.. Pin
Marcel Härry22-Jun-05 12:20
Marcel Härry22-Jun-05 12:20 
GeneralRe: two issues.. Pin
skjpatel9-May-06 0:32
skjpatel9-May-06 0:32 
GeneralSystem.DelegateSeriliazationHolder are not permitted to be deserialized at this security level Pin
evsyukur19-Sep-04 22:25
evsyukur19-Sep-04 22:25 
GeneralSuggestion Pin
xidar11-Dec-03 3:28
xidar11-Dec-03 3:28 
GeneralError while compiling the code Pin
AxelB29-May-03 12:31
AxelB29-May-03 12:31 
GeneralRe: Error while compiling the code Pin
Anthony Roach29-May-03 22:06
Anthony Roach29-May-03 22:06 
GeneralRe: Error while compiling the code Pin
AxelB29-May-03 22:38
AxelB29-May-03 22:38 
General.NET Frameword version 1.1 Pin
d1hawkins20-May-03 8:50
d1hawkins20-May-03 8:50 
GeneralRe: .NET Frameword version 1.1 Pin
Anthony Roach29-May-03 22:08
Anthony Roach29-May-03 22:08 
GeneralRe: .NET Frameword version 1.1 Pin
sjohnst44-Jun-03 0:05
sjohnst44-Jun-03 0:05 
GeneralRe: .NET Frameword version 1.1 Pin
4-Jun-03 0:35
suss4-Jun-03 0:35 
GeneralThank god! Pin
ThePhantomMenace16-Apr-03 23:27
ThePhantomMenace16-Apr-03 23:27 
GeneralHard to read Pin
Sean Winstead20-Jan-03 8:33
Sean Winstead20-Jan-03 8:33 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.