Updated to reflect Feb CTP changes in the config model.

In Longhorn we are implementing many enhancements to the Peer to Peer programming platform one of the most exciting to me is the Peer channel.   The discussion that follows describes pre-release software, specifically it describes the implementation of Peer Channel that ships on the CD for delegates to the 2005 PDC. 

You can also download the September CTP WinFX runtime from here:

What is the Peer Channel ?

The Peer Channel is an Indigo channel that supports multi-party communication.  A typical Indigo channel has 2 participants, a Peer Channel can have any number of participants.  A message sent by one particpant will be recieved by all participants.

Node   - A single instance of an application that operates in a Peer to Peer application
Mesh   - A group of nodes that are connected together and form a Peer to Peer application.
ServiceContract   - The definition for a group of related messages.  This definition is usually realized as a ManagedCode interface, the signature of the methods and attributes control the actual message format.  This definition may also be realized as a WSDL contract.
OperationContract   - The definition for a single operation may refer to the incoming and outgoing messages for a single operation.
InstanceContext    - The managed code class that handles incoming messages.
Proxy   - The managed code class that is used to generate outgoing messages.

A simple Peer Channel application

Lets look at a simple Peer Channel App that allows a message to be broadcast and recieved by all members of a mesh .  Lets call the program HelloMesh.

The Contract

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

interface IHelloMesh {

   [OperationContract(IsOneWay = true)]

   void Say(string message);

}

This contract describes a single operation called Say.  The operation Say has one argument which is a string. 

The ServiceContract attribute specifies the XmlNamespace for the message which in this case is a guid that I used Guidgen to generate, guids are not very user friendly as names since they are hard to type in this case though the guid makes a good namespace since the managed programming model never surfaces it, so a developer doesn’t need to type it.  The ServiceContract also specifies that the Contract has a CallbackContract which is of the same type.  Therefore this is a symmetrical contract meaning the outgoing and incoming messages have the same format, because the CallbackContract is specified it is only usable by Duplex (2 way) channels.

The Proxy

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

   public HelloProxy(InstanceContext inputInstance, string name) : base(inputInstance, name) {

      base.InnerDuplexChannel.Open();

   }

   public void Say(string message) {

      base.InnerProxy.Say(message);

   }

}

The proxy is used by client code to send messages to the mesh, the majority of work is done by the base classes, the proxy class must, however, provide an implementation of the IHelloMesh contract which delegates the actual sending of the message to the InnerProxy.  A constructor is also provided, in it we Open the DuplexChannel.  If the channel is not opened here then it will be opened when the first message is sent accross the channel - in our case that is not what we want because a client would not be able to recieve a message until after it had sent on, so we Open the channel when we construct and everything works as you would expect.  The constructor takes 2 arguments inputInstance which is an instance of the class which handles inbound messages, and name which is the name of the endpoint description to read from the app.config file.

The InstanceContext

This is the class that is used to handle inbound messages.  It has an Implementation of the ServiceContract.

class HelloMesh : IHelloMesh {

   public void Say(string message) {

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

   }

}

The Application

The application is trivial to write, you just 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. 

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

         proxy.Say(msg);

      }

      proxy.Close();

     }

   }

}

The Complete Program:

using System;

using System.Collections.Generic;

using System.ServiceModel;

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) : 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 Main(string[] args) {

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

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

            string msg = "";

            while (msg != "q") {

               msg = Console.ReadLine();

               proxy.Say(msg);

            }

            proxy.Close();

         }

      }

   }

}

The Configuration

The last part of the application's source code is the configuration:

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/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>

This file defines one endpoint called HelloEndpoint and one binding called Binding1.  The binding specifies the port, the endpoint specifies the uri for the mesh.

To compile this you will need to reference System.ServiceModel.dll.

Longhorn when the application is run on Windows XP or Longhorn the Firewall software will pop a dialog asking if you want to unblock the application, this occurs because it is a communications application and is trying to access a port.  It is okay to unblock this application, select Unblock.

If this app is run on a machine that doesn't have PNRP enabled an InvalidOperationException will be thrown with the message:

System.InvalidOperationException: Resolver must be specified. The default resolver (PNRP) is not available.

If your system is running Longhorn or Windows XP with SP2 then enable Pnrp: Details

I think you can see that creating a multiparty app is very easy using Indigo and the PeerChannel.  And also that your apps can be written in a modular way with clearly defined roles for the various classes in your application.

Next time I will show you how to write the same application with 2 one way channels rather than a DuplexChannel.