|
The .NET (C#) client and server can be compiled from within the Visual Studio IDE by opening the Subscription.sln
solution file.
The publisher program is entirely implemented in the Publisher\Publisher.cs
file, which is dissected in detail below.
First the typical set of namespaces is imported:
using System; using System.Threading; using Inspirel.YAMI;
In addition to the classes already discussed in other examples, the Inspirel.YAMI
namespace defines the ValuePublisher
class that is used for managing data-publication entities. Each instance of this class represents a single publishing data source, as shown in the publisher code:
namespace Subscription { class Publisher { static void Main(string[] args) { if(args.Length != 1) { Console.WriteLine( "expecting one parameter: " + "publisher destination"); return; } string publisherAddress = args[0]; try { ValuePublisher randomValue = new ValuePublisher();
The randomValue
object declared above will be later registered as a regular YAMI4 object that can be seen by remote clients.
Agent publisherAgent = new Agent(); string resolvedAddress = publisherAgent.AddListener(publisherAddress); Console.WriteLine( "The publisher is listening on {0}", resolvedAddress);
The value publisher object is registered as a regular object.
In this example the "random_number"
is the name that will be seen by remote clients.
publisherAgent.RegisterValuePublisher( "random_number", randomValue);
The publisher performs a simple work of generating random numbers every second and publishing them to the currently subscribed clients.
All values that are published need to be expressed in terms of the parameters object, which is consistent with the general YAMI4 data model. In this example a single random value is published as an integer entry in the parameters object:
// publish random values forever Parameters content = new Parameters(); Random generator = new Random(); while(true) { int random = generator.Next(0, 100); content.SetInteger("value", random); Console.WriteLine("publishing value {0}", random); randomValue.Publish(content);
The above operation is asynchronous just like any other message-sending operation. Internally, it scans the list of currently subscribed clients and sends them an update message with the given parameters object. The actual messages are sent in background.
The publisher's main loop involves one-second delay, but in practical programs data publishing need not be periodic.
Thread.Sleep(1000); }
The publisher concludes with basic exception handling.
} catch(Exception ex) { Console.WriteLine( "error: {0}", ex.Message); } } } }
The subscriber program is implemented in a single Subscriber\Subscriber.cs
file.
Subscribers have both client- and server-side properties in the sense that they need to be able to both send a subscription message to the publisher - which is the client-side activity - and to receive the updates - which makes them act like servers.
The example subscriber code starts with relevant imports:
using System; using System.Threading; using Inspirel.YAMI; namespace Subscription { class Subscriber {
To enable the subscriber to receive subscription updates, the appropriate message handler needs to be implemented. As with other servers, this is achieved with a delegate, which protocol is defined by Agent.IncomingMessageHandler
.
The subscriber simply prints the received value on the console:
private static void updateHandler( object sender, IncomingMessageArgs args) { Parameters content = args.Message.Parameters; int value = content.GetInteger("value"); Console.WriteLine("received update {0}", value); }
The main part of the program obtains the publisher target from its command-line arguments and registers the message handler to enable proper routing of subscription updates.
An important property of this program is that even though it technically acts like a server (it receives updates via registered message handler), it does not have any listener. The listener is not needed here, because the subscription updates will be sent back using the same communication channel that is initially used for sending the subscription message - this is an example of reverting the channel's natural direction.
static void Main(string[] args) { if(args.Length != 1) { Console.WriteLine( "expecting one parameter: " + "publisher destination"); return; } String publisherAddress = args[0]; try { Agent subscriberAgent = new Agent(); // prepare subscription update callback string updateObjectName = "update_handler"; subscriberAgent.RegisterObject( updateObjectName, updateHandler);
Once the message handler is registered as a regular object, the subscription message can be sent to the publisher.
It is important to properly inform the publisher of the intended object name where the updates are to be delivered - here, the object is registered with the name "update_handler"
and this name is sent to the publisher as the "destination_object"
entry in the subscription message's payload:
// subscribe to the producer Parameters param = new Parameters(); param.SetString( "destination_object", updateObjectName); subscriberAgent.SendOneWay(publisherAddress, "random_number", "subscribe", param); Console.WriteLine( "subscribed, waiting for updates");
Above, the message that is sent to publisher is named ``subscribe'', which is automatically recognized by the value publisher object. Another recognized name is ``unsubscribe'', which causes the given subscriber to be removed from the list.
After all these preparations, the subscriber is ready to receive updates and falls into an infinite loop.
// block forever // and receive updates in background while(true) { Thread.Sleep(10000); } } catch(Exception ex) { Console.WriteLine( "error: {0}", ex.Message); } } } }