Welcome to MSDN Blogs Sign in | Join | Help

Automatic port number generation

It has been an age since I updated this, the reason is quite simple - My team has been working hard to get Peer Channel ready to ship.

One of my favourite of the features we have added recently is now available in the latest CTP (download here) is the ability to tell the framework to automagically assign a random port to a PeerNode.

There are several benefits with automatically generating the port number:

  1. It is easy to run multiple instances of the application on a single machine
  2. It is harder to attack an application if it is not on a fixed port, because an application can't tell just by observing what port's are open on a machine what applications are running on it.

To automatically assign is really quite easy the NetPeerTcpBinding instance has a Port property if you set that to zero before opening the channel then the system will automatically generate a port randomly and set the peernode to listen on that port.

This can also be easily done in a configuration file for example to modify the sample in the post Is there anybody out there? (Online /Offline and the Peer channel)

The configuration file simply changes to this:

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

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

  <system.serviceModel>

    <client>

      <endpoint name="HelloEndpoint"

                address="net.p2p://hellomesh/hellomesh"

                binding="netPeerTcpBinding"

                bindingConfiguration="Binding1"

                contract="HelloMesh.IHelloMesh">

      </endpoint>

    </client>

 

    <bindings>

      <netPeerTcpBinding>

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

          <security mode="None"/>

        </binding>

      </netPeerTcpBinding>

    </bindings>

  </system.serviceModel>

</configuration>


Now you can run as many instances as you want on each computer without recieving the Address in use exception that indicates that you are trying to use the same port in two applications.

 

Until next time

Posted by Kevin Ransom | 0 Comments

Is there anybody out there? (Online /Offline and the Peer Channel)

*********************************************************

Updated to reflect changes in the February CTP

*********************************************************

One of the most frequently asked questions is this: How can I tell if a node is the only member of the mesh?  We addedevents and a property that can be queried to get this information

The definition of online status is straightforward:

  • A node that has no immediate neighbors is Offline.
  • A node with at least one neighbor is Online.

 

PeerNode and Online Status

To set the online and offline events to handlers specified in your own code use:

IOnlineStatus status = channel.GetProperty<IOnlineStatus>();

status.Online += online;

status.Offline += offline;

The handler is a method of type EventHandler very simple handlers might look like:

static void Online(object o, EventArgs args)

{

  Console.WriteLine("Online");

}

 

static void Offline(object o, EventArgs args)

{

  Console.WriteLine("Offline");

}

It should be noted that in Beta 1 and the forthcoming Beta 2 release of Peer Channel the handlers are not called on the UI thread so you must take care to ensure that you write thread safe code for these handlers.  If you try not to change global or static state you should be all right, the 2 most common uses of these events are to 1. Send a message to the mesh or 2 to toggle an indicator on the UI.

If instead of responding to an event you prefer to query the status then use the IsOnline property true means online false means offline:

IOnlineStatus status = channel.GetProperty<IOnlineStatus>();

if (status.IsOnline)

  Console.WriteLine("Online");

 

 

Non-Guarantees

It should be noted that the online status is subject to change at any time; it is eminently possible for you to receive an Online event during which you send a message to the mesh and then before the message has been transmitted to any neighbors for the node to go offline again.

Online is fired when a node becomes Online, Offline is fired when a node loses it’s last neighbor.  Offline is not fired during Open if the node is opened and does not have any neighbors.

The following example shows how to create a proxy and specify handlers for the online and offline events, this is not the only way to do it but it is the way I like and use.

 

The contract

The contract is the same as before

[ServiceContract(Namespace = "urn:uuid:F0E619F9-3AB7-4b9d-A009-269B381C6C85/", CallbackContract = typeof(IHelloMesh))]

interface IHelloMesh

{

  [OperationContract(IsOneWay = true)]

  void Say(string message);

}

 

The proxy

Add an overloaded Constructor to HelloProxy that sets the events to handlers passed into the constructor:

public HelloProxy(InstanceContext inputInstance, string name, EventHandler online, EventHandler offline)

       : base(inputInstance, name)

{

  IOnlineStatus status = base.InnerChannel.GetProperty<IOnlineStatus>();

  status.Online += online;

  status.Offline += offline;

  base.InnerDuplexChannel.Open();

}

 

Add the message handler

class HelloMesh : IHelloMesh

{

  public void Say(string message)

  {

    Console.WriteLine("Say : {0}", message);

  }

}

 

Add handlers to the main program:

 

static void Online(object o, EventArgs args)

{

  Console.WriteLine("Online");

}

 

static void Offline(object o, EventArgs args)

{

  Console.WriteLine("Offline");

}

And that is all there is to it.  Now when your application starts it will display Online in the output  as soon as it is connected to a neighbor; when it is completely alone it will produce Offline in the output.

 

The complete program

using System;

using System.Collections.Generic;

using System.ServiceModel;

using System.ServiceModel.Channels;

using System.Text;

 

namespace HelloMesh

{

  [ServiceContract(Namespace = "urn:uuid:F0E619F9-3AB7-4b9d-A009-269B381C6C85/", CallbackContract = typeof(IHelloMesh))]

  interface IHelloMesh

  {

    [OperationContract(IsOneWay = true)]

    void Say(string message);

  }

 

  partial class HelloProxy : System.ServiceModel.DuplexClientBase<IHelloMesh>, IHelloMesh

  {

    public HelloProxy(InstanceContext inputInstance, string name, EventHandler online, EventHandler offline)

           : base(inputInstance, name)

    {

      IOnlineStatus status = base.InnerChannel.GetProperty<IOnlineStatus>();

      status.Online += online;

      status.Offline += offline;

      base.InnerDuplexChannel.Open();

    }

 

    public HelloProxy(InstanceContext inputInstance, string name)

           : base(inputInstance, name)

    {

      base.InnerDuplexChannel.Open();

    }

 

    public void Say(string message)

    {

      base.InnerProxy.Say(message);

    }

  }

 

  class HelloMesh : IHelloMesh

  {

    public void Say(string message)

    {

      Console.WriteLine("Say : {0}", message);

    }

  }

 

  class Program

  {

    static void Online(object o, EventArgs args)

    {

      Console.WriteLine("Online");

    }

 

    static void Offline(object o, EventArgs args)

    {

      Console.WriteLine("Offline");

    }

 

    static void Main(string[] args)

    {

      InstanceContext instanceContext = new InstanceContext(new HelloMesh());

      using (HelloProxy hello = new HelloProxy(instanceContext, "HelloEndpoint", Online, Offline))

      {

        string msg = "";

        while (msg != "q")

        {

          msg = Console.ReadLine();

          hello.Say(msg);

        }

        hello.Close();

      }

    }

  }

}

The config is unchanged from before and looks like:

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

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

  <system.serviceModel>

    <client>

      <endpoint name="HelloEndpoint"

                address="net.p2p://hellomesh/hellomesh"

                binding="netPeerTcpBinding"

                bindingConfiguration="Binding1"

                contract="HelloMesh.IHelloMesh">

      </endpoint>

    </client>

 

    <bindings>

      <netPeerTcpBinding>

        <binding name="Binding1"

              port="5182">

          <security mode="None"/>

        </binding>

      </netPeerTcpBinding>

    </bindings>

  </system.serviceModel>

</configuration>

 

Next:

Possibly:

   Structured data (DataContracts and Peer Channel)

   Sending a message only to your nearest neighbor (Message Filters) !!!!!

   Channels Input/Output/Duplex  and Peer Channel

           

Posted by Kevin Ransom | 1 Comments

Handling Messages directly

*********************************************************

Updated to reflect changes in the February CTP

*********************************************************

My previous samples demonstrated using ServiceModel to dispatch messages to a strongly typed message handlers.  There are times when instead of having a seperate implementation for every message you will want to have a centralised message handler (often this handler is called ProcessMessage).  An early version of the Peer channel system code had a message handler called ProcessMessage where the Peer channel messages were dispatched.

A Strongly Typed contract

[ServiceContract(Namespace = "urn:uuid:F0E619F9-3AB7-4b9d-A009-269B381C6C85/", CallbackContract = typeof(IHelloMesh))]

interface IHelloMesh {

    [OperationContract(IsOneWay = true)]

    void Say(string message);

}

This is considered strongly typed because the method Say takes a string as an argument.

The Contract

This contract defines a a handler that recieves raw messages, it will recieve messages for any action.

public static class ProcessMessageActions

{

  public const string SayAction = "Say";

}

 

[ServiceContract(Namespace = "urn:30E9233A-6C5C-47a2-8E90-93D592DA0A00/", CallbackContract = typeof(IProcessMessage))]

interface IProcessMessage

{

  [OperationContract(Action="*", IsOneWay = true)]

  void ProcessMessage(Message message);

}

The  Action="*" option on the OperationContract attribute is called the wildcard action it says that any value in the Headers.Action property of the message will be handled by the message handler specified by this contract, and the argument type is Message;  Message is a new abstraction in the System.ServiceModel namespace that allows a developer to operate on raw messages when necessary, such as adding or retrieving Message Headers, generating or retrieving the raw Xml.

The ProcessMessageActions class defines the actions to be used in a convenient manner.

The Proxy

class HelloMesh : IProcessMessage

{

  public void ProcessMessage(Message message)

  {

    if (message.Headers.Action == ProcessMessageActions.SayAction)

    {

      string data = message.GetBody<string>();

      Console.WriteLine(data);

    }

  }

}

Again the proxy does little more than delegate to the base implementation.  This time the Method is called ProcessMessage and it takes a a Message as its argument. It can be used to create and read the elements of the message including headers.

The InstanceContext

This is the class that is used to handle inbound messages.  It has an Implementation IProcessMessage in this case it verifies that the action matches a specific value, if so it does the work associated with that action.

class HelloMesh : IProcessMessage

{

  public void ProcessMessage(Message message)

  {

    if (message.Headers.Action == ProcessMessageActions.SayAction)

    {

      string data = message.GetBody<string>();

      Console.WriteLine(data);

    }

  }

}

The Application

The application is still trivial to write, as before you create an InstanceContext for incoming messages, a proxy for outgoing messages, and then in a loop read what to send, and then use the proxy to send it. In this case it is necessary to create a message object with the data.

class Program

{

  static void Main(string[] args)

  {

    InstanceContext instanceContext = new InstanceContext(new HelloMesh());

    using (HelloProxy proxy = new HelloProxy(instanceContext, "HelloEndpoint"))

    {

      string msg = "";

      while (msg != "q")

      {

        msg = Console.ReadLine();

        Message message = Message.CreateMessage(MessageVersion.Soap12WSAddressing10, ProcessMessageActions.SayAction, msg);

        proxy.ProcessMessage(message);

      }

      proxy.Close();

    }

  }

}

The Configuration

The only changes necessary to the config are because we renamed the service contract and I also changed the mesh address:

To enable certain changes to be made after the application has been deployed for instance port, the endpoint and binding are usually specified in the app.config file.  This is the config file for our application above.

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

<configuration xmlns="http://schemas.microsoft.com/.NetConfiguration/v2.0">

  <system.serviceModel>

    <client>

      <endpoint name="HelloEndpoint"

         address="net.p2p://hellomesh/processmessage"

         binding="netPeerTcpBinding"

         bindingConfiguration="Binding1"

         contract="HelloMesh.IProcessMessage">

      </endpoint>

    </client>

    <bindings>

      <netPeerTcpBinding>

        <binding name="Binding1"

              port="5182">

          <security mode="None"/>

        </binding>

      </netPeerTcpBinding>

    </bindings>

  </system.serviceModel>

</configuration>

Putting the code together in one piece leaves us witt:

The Complete Program:

using System;

using System.Collections.Generic;

using System.ServiceModel;

using System.ServiceModel.Channels;

using System.Text;

 

namespace HelloMesh

{

  public static class ProcessMessageActions

  {

    public const string SayAction = "Say";

  }

 

  [ServiceContract(Namespace = "urn:30E9233A-6C5C-47a2-8E90-93D592DA0A00/", CallbackContract = typeof(IProcessMessage))]

  interface IProcessMessage

  {

      [OperationContract(Action="*", IsOneWay = true)]

      void ProcessMessage(Message message);

  }

 

    partial class HelloProxy : System.ServiceModel.DuplexClientBase<IProcessMessage>, IProcessMessage

  {

      public HelloProxy(InstanceContext inputInstance, string configurationName) : base(inputInstance, configurationName)

      {

          base.InnerDuplexChannel.Open();

      }

 

      public void ProcessMessage(Message message)

      {

          base.InnerProxy.ProcessMessage(message);

      }

   }

 

   class HelloMesh : IProcessMessage

   {

      public void ProcessMessage(Message message)

      {

         if (message.Headers.Action == ProcessMessageActions.SayAction)

         {

            string data = message.GetBody<string>();

            Console.WriteLine(data);

         }

      }