The PeerHopCount Attribute: Controlling Message Distribution

This post explains the how, what, and why of the PeerHopCount attribute provided by Peer Channel in the .NET Framework 3.0

Why would I need PeerHopCount?

Peer Channel’s basic flooding model involves distributing each message sent by any member of a mesh to all other members of that mesh. This is ideal in situations where every message generated by a client is relevant and useful for all other clients (e.g. chat room). However, there exist applications in which certain messages may be intended for limited distribution throughout the mesh. For example, if a new client joins a mesh and wants to retrieve the last message sent through the mesh, this request does not need to be flooded to every member of the mesh. In this case, it would be much more appropriate to send such a request to a small section of the mesh. This behavior is possible using the PeerHopCount attribute.

What does PeerHopCount do?

The concept of PeerHopCount is similar to TTL (Time-To-Live) used in the IP protocol. The value of the PeerHopCount is tied to a message instance, and it specifies how many times that message should be forwarded before being dropped. Each time a message is received by a Peer Channel client, it examines the message to see whether PeerHopCount is specified. If it is specified, that value is decremented by 1. If the value of a received message’s PeerHopCount is zero, the message is still received by the application, but it is not forwarded to the receiving node’s neighbors. Consider the following simple chain of nodes:

A à B à C à D

Suppose node A sends a message with a PeerHopCount of 2. The message is sent to A’s neighbor, node B. Upon reception, node B will decrement the PeerHopCount to 1. Since this value is still greater than 0, this message will be sent to node B’s neighbor. When node C receives the message, it too will decrement the PeerHopCount, this time setting it to 0. Since the new PeerHopCount value is 0, node C will not forward the message to the node D. Note that node C’s application will still receive and be able to process the message.

In summary, if node A sends a message with PeerHopCount of 2, that message will be received by nodes B and C, but not the rest of the mesh. If node A sent a message with a PeerHopCount of 1, only node B would receive the message – nodes C and D would not receive the message.

How do I use PeerHopCount?

Using PeerHopCount simply requires labeling a field or property on a message class with the PeerHopCount attribute. This value is then treated as the PeerHopCount value for that message once it is sent out. The following is an example of a ChatMessage class using the PeerHopCount attribute:

[MessageContract]

public class ChatMessage

{

    [PeerHopCount]

    public int HopsLeft;

    [MessageBodyMember]

    public string Text;

    [MessageBodyMember]

    public string Source;

    public ChatMessage() { }

    public ChatMessage (string source, string text, int hops)

    {

        Source = source;

        Text = text;

        HopsLeft = hops;

    }

}

As seen above, the HopsLeft field on the ChatMessage class is declared with the PeerHopCount attribute. The other two members contain the text of the message, as well as the name of the node who sent the message. We also use the MessageContract and MessageBodyMember attributes to allow the framework to easily format the message on the wire.

From this point out, I will walk through the remaining steps of how to use this ChatMessage class in a sample chat application. Here is the service contract with which we will use the ChatMessage class:

[ServiceContract(Namespace = "https://Microsoft.ServiceModel.Samples", CallbackContract = typeof(IChat))]

public interface IChat

{

    [OperationContract(IsOneWay = true)]

    void Join(string member);

    [OperationContract(IsOneWay = true)]

    void Chat(ChatMessage msg);

    [OperationContract(IsOneWay = true)]

    void Leave(string member);

}

public interface IChatChannel : IChat, IClientChannel

{

}

Above we define three simple methods to be used in a typical chat scenario (a mesh is a group of nodes connected to each other): Join (sent by a new node to announce presence in the mesh), Chat (sending a message to the other nodes in the mesh), and Leave (announcing departure from the mesh). The channel we will use is called IChatChannel, and uses our IChat service contract and IClientChannel from .NET 3.0 to create a full client channel with IChat semantics.

Below you will find the main application code and config file, which are similar to the PeerChat sample in the .NET 3.0 SDK, modified to include our use of the PeerHopCount attribute. If you have a beta release of .NET 3.0 installed, you should be able to run this sample on your own computer. Follow these steps:

1. Create a new C# Console Application in Visual Studio

2. Copy and paste all code from this blog entry into the Default Program.cs – you will need to make sure your namespace matches the service contract and config (easiest thing to do is use Microsoft.ServiceModel.Samples instead of the default namespace)

3. Add a config file to the project by selecting Project->Add->Add New Item. Select “Application Configuration File”

4. Copy and paste the config information below into your App.config file.

5. If you are running Windows XP, make sure you have running PNRP.

6. Build the project and go!

Onto the main code and config:

// Copyright (c) Microsoft Corporation. All Rights Reserved.

 

using System;

using System.Configuration;

using System.ServiceModel;

using System.Collections.Generic;

using System.ServiceModel.PeerResolvers;

using System.Runtime.Serialization;

    public class ChatApp : IChat

    {

        // member id for this instance

  string MemberName;

        IChatChannel participant;

        public ChatApp(string member)

        {

            this.MemberName = member;

        }

        // Host the chat instance within this EXE console application.

        public static void Main()

        {

            Console.Write("Enter your nickname: ");

            string member = Console.ReadLine();

            int MessageHops = 3;

           

            // Construct InstanceContext to handle messages on callback interface.

            // An instance of ChatApp is created and passed to the InstanceContext.

            ChatApp instance = new ChatApp(member);

            InstanceContext instanceContext = new InstanceContext(instance);

            // Create the participant with the given endpoint configuration

            // Each participant opens a duplex channel to the mesh

            // participant is an instance of the chat application that has opened a channel to the mesh

            DuplexChannelFactory<IChatChannel> factory = new DuplexChannelFactory<IChatChannel>(instanceContext, "ChatEndpoint");

           

            instance.participant = factory.CreateChannel();

            try

            {

                instance.participant.Open();

            }

            catch (CommunicationException)

            {

                Console.WriteLine("Could not find resolver.");

                return;

            }

           

            Console.WriteLine("{0} is ready", member);

            Console.WriteLine("Press q<ENTER> to terminate this instance.");

            // Announce self to other participants

            instance.participant.Join(member);

            // loop until the user quits

            while (true)

            {

                string MessageText = Console.ReadLine();

                if (MessageText == "q") break;

                    instance.participant.Chat(new ChatMessage(member, MessageText, MessageHops));

            }

            // Leave the mesh

            instance.participant.Leave(member);

            instance.participant.Close();

            factory.Close();

        }

        // ********************

        // IChat implementation - This is where we define the functions we prototyped in our service contract

        // ********************

        //

        public void Join(string member)

        {

            Console.WriteLine("[{0} joined]", member);

        }

        public void Chat(ChatMessage msg)

        {

                Console.WriteLine("[{0}] {1} ({2} hops left)", msg.Source, msg.Text, msg.HopsLeft);

        }

        public void Leave(string member)

        {

            Console.WriteLine("[{0} left]", member);

        }

    }

}

Finally, here are the contents of App.config:

<?xml version="1.0" encoding="utf-8" ?>

<configuration>

    <system.serviceModel>

      <client>

         <!-- chat instance participating in the mesh -->

         <endpoint name="ChatEndpoint"

                   address="net.p2p://chatMesh/ServiceModelSamples/Chat"

                   binding="netPeerTcpBinding"

                   bindingConfiguration="BindingDefault"

                   contract="Microsoft.ServiceModel.Samples.IChat">

         </endpoint>

      </client>

      <bindings>

         <netPeerTcpBinding>

           <binding name="BindingDefault" port="0">

             <security mode="None"/>

             <resolver mode="Auto"/>

           </binding>

         </netPeerTcpBinding>

      </bindings>

   </system.serviceModel>

</configuration>

And that’s it!! By changing the MessageHops variable in Main(), you can control how chat messages travel throughout the mesh. Enjoy! Feel free to contact us if you have any questions. -Jonathan