Using Peer Channel


This chapter explains Peer Channel by showing how to use it to construct an application for conducting a quiz in a classroom. In the scenario, each pupil has a computer program that can receive the questions in the quiz from a software application that the teacher has. The pupils respond to the quiz questions using their program, and their responses are transmitted to the teacher, who can grade the pupils' answers as they come in, and transmit grades back to each individual class member. The teacher can also broadcast instructions to the entire class.

This scenario is not a good one in which to apply an instant messaging solution. Besides the tedium involved in the teacher having to type out each question, the pupils' application could do little more than simply show the pupil what the teacher typed. It would be better for the teacher's application to transmit structured data to the pupils' application, which could then meaningfully process the information that it received and display it accordingly. Of course, one could transmit strings of XML between the applications via an instant messaging API like the real-time communications client API, but then one would have to write code to parse the XML. As will soon become apparent, considerable effort will be saved when the Windows Communication Foundation's Peer Channel is used to implement the solution.

Be aware that to use Peer Channel, one's computer system must have a network connection. That is necessary even for communication among applications residing together on that system.

Envisaging the Solution

Figure 11.1 shows the user interface of the teacher's application. As the pupils start their applications, each pupil's application transmits the pupil's name and photograph to the teacher's application, which displays the photographs in the area at the top. When the teacher sees that all the pupils have started their applications, and are therefore ready to begin the quiz, the teacher clicks on the Start button to transmit the quiz questions to the students. There is a box for text entry along with a Send button for broadcasting announcements to the class. As the students answer the quiz questions, their responses are displayed in the box in the Grading section of the screen. The teacher can scroll backward and forward through the responses that have been received, and indicate, for each response, whether it is correct or incorrect. The teacher can then click the Grade button to send a message to the application of the pupil who submitted the response, indicating whether the pupil's response was correct.

Figure 11.1. The teacher's application.


The user interface of the pupils' application is shown in Figure 11.2. It has an area on the left to display a picture, an area at the top to display an instruction, and a set of radio buttons to show the possible answers, with a button to submit a selected answer. When the teacher has graded a particular answer, a check mark shows up next to the radio buttons if the answer is correct, and a cross shows up if the answer is incorrect. There are buttons to navigate back and forth through the questions.

Figure 11.2. The pupil's application.


Figure 11.3 depicts one possible sequence of messages that may be exchanged. There are really only two rules, though, governing the sequence of messages. The first rule is that the pupils' application must send the teacher's application a Join message before the teacher's application can begin transmitting AddItem messages to the pupil with the quiz questions. The second rule is that the pupils' application must send a Response message with the answer to a question before the teacher's application can send a Response message with the teacher's assessment of the answer.

Figure 11.3. One possible sequence of messages.


Almost all the data exchanged between the teacher's application and the pupils' application via these messages is structured:

  • The message that each pupil's application transmits to the teacher's application when it starts consists of a picture and a name. The teacher's application unpacks that message and displays the picture, while using the name, in the background, to open a private connection with that pupil's application.

  • Each quiz question that the teacher's application transmits to the pupils' application consists of a picture, an instruction, and set of possible answers for the pupil to choose from. The pupils' application unpacks the messages containing the questions, and displays each element in the appropriate area of the pupils' user interface.

  • When a pupil's application conveys an answer to a question to the teacher's application, the message consists of the pupil's name, the identifier of the question being answered, and the answer itself. The teacher's response to the pupil indicating whether the response was correct or incorrect conveys the identifier of the question that the pupil answered and whether the pupil's answer was correct. The pupil's application reads that message, identifies the quiz item to which it pertains, and updates it with the teacher's evaluation of the pupil's answer.

Only the announcements that the teacher broadcasts to the class during the quiz constitute unstructured data.

Designing the Messages and the Message Exchange Patterns

To translate this design of how the teacher's and pupils' applications will interact with one another into code, follow these steps:

1.

Copy the code associated with this chapter that you downloaded from www.samspublishing.com to the folder C:\WCFHandsOn. The code is all in a folder called PeerChannel. After the code has been unzipped, there should be a folder that looks like the one shown in Figure 11.4.

Figure 11.4. PeerChannel folder.


2.

Open the solution C:\WCFHandsOn\PublishSubscribe\Start\PeerChanel.sln. It consists of two Windows projects: one called Child for building the pupils' application, and another called Teacher, for building the teacher's application.

3.

Add a class module called Quiz.cs to the Teacher project. Modify its contents so that it looks like the code in Listing 11.1.

Listing 11.1. Message Definitions

using System; using System.Collections.Generic; using System.Drawing; using System.Runtime.Serialization; using System.ServiceModel; using System.Text; namespace WindowsCommunicationFoundationHandsOn.School {     public struct Student     {         public string Name;         public byte[] Image;     }     public struct QuizItem     {     public string Identifier;     public byte[] ItemImage;     public string Text;     public QuizResponse[] Responses;     public bool Submitted;     public string ResponseText     {         get         {              foreach (QuizResponse response in Responses)              {                  if (response.Submitted)                  {                       return response.ResponseText;                  }              }              return null;         }     }     public bool? Correct     {         get         {             foreach (QuizResponse response in Responses)             {                 if (response.Submitted)                 {                     return response.Correct;                 }             }             return null;            }         }     }     public struct QuizResponse     {         public string ResponseIdentifier;         public Student Responder;         public QuizItem Item;         public string ResponseText;         public bool? Correct;         public bool Submitted;     } }

Thus we define structs to represent the messages to be exchanged between the teacher's application and the students' application. The Student struct represents the message, with the pupil's name and photograph, that the pupil's application will send to the teacher's application to signal that the pupil has started the application.

The QuizItem struct represents the message that the teacher's application will send to the pupils' application containing a quiz question. The Identifier, ItemImage, Text, and Responses members of that class represent the identifier of the quiz question, the picture to which the question pertains, the instruction, and various possible responses. The Submitted member will be used internally in the pupils' application to track whether the response to the question that the pupil selected has been submitted to the teacher.

The QuizResponse struct represents the message from the pupils' application to the teacher's application conveying the pupil's response, as well as the message from the teacher's application to the pupils' application with the teacher's assessment of the response. The Responder member identifies the pupil who is responding to the question. The Item member indicates which quiz question is being answered. The ResponseText member contains the pupil's answer. The Correct member contains the teacher's evaluation of the pupil's answer. The Submitted member will be used internally in the pupil's application to track whether the response is the pupil's chosen response, and by the teacher's application to track whether the teacher's assessment of the response has been transmitted to the pupil who submitted it.

4.

Now add these definitions to the Quiz.cs module:

public interface IQuizManagement {      void Join(Student student);      void Announce(string announcement); } public interface IQuizQuestion {      void AddItem(QuizItem item); } public interface IQuizResponse {      void SendResponse(QuizResponse response); }


These interfaces define the simple message exchange patterns required for the solution. The IQuizManagement interface's Join() method represents the pupil's application sending a message to the teacher's application to announce that the pupil has started the application and is ready for the quiz. The Announce method of that interface represents the teacher broadcasting an announcement message to the pupil. The IQuizQuestion interface's sole AddItem method represents the teacher's application sending a quiz question to the pupil's application, and the IQuizResponse's sole SendResponse method represents the pupil's application sending one of the pupil's answers to the teacher's application, and the teacher sending an evaluation of that answer back to the pupil.

Now that the messages and the patterns for exchanging them have been defined, the Windows Communication Foundation's Peer Channel can be used to transmit the messages between the teacher's application and the pupils'.

5.

Add DataContract attributes to the structs, and DataMember attributes to certain of their fields, as shown in Listing 11.2. The attributes indicate to the Windows Communication Foundation which members of which data types are to be incorporated in the messages that will be conveyed via the methods defined by the interfaces.

Listing 11.2. Data Contract Definitions

[DataContract] public struct Student {    [DataMember]    public string Name;    [DataMember]    public byte[] Image; } [DataContract] public struct QuizResponse {    [DataMember]    public Student Responder;    [DataMember]    public QuizItem Item;    [DataMember]    public string ResponseText;    [DataMember]    public string ResponseIdentifier;    [DataMember]    public bool? Correct;    public bool Submitted; } [DataContract] public struct QuizItem {    [DataMember]    public string Identifier;    [DataMember]    public byte[] ItemImage;    [DataMember]    public string Text;    [DataMember]    public QuizResponse[] Responses;    public bool Submitted;    public string ResponseText    {        get        {            foreach (QuizResponse response in Responses)            {                if (response.Submitted)                {                    return response.ResponseText;                }            }            return null;     } } public bool? Correct {     get     {         foreach (QuizResponse response in Responses)         {             if (response.Submitted)             {        return response.Correct;             }          }          return null;       }    } }

6.

Modify the definitions of the interfaces in the Quiz.cs module in this way:

[ServiceContract(CallbackContract = typeof(IQuizQuestion))] [PeerBehavior] public interface IQuizQuestion {     [OperationContract(IsOneWay = true)]     void AddItem(QuizItem item); } [ServiceContract(CallbackContract = typeof(IQuizResponse))] [PeerBehavior] public interface IQuizResponse {    [OperationContract(IsOneWay = true)]    void SendResponse(QuizResponse response); } [ServiceContract(CallbackContract = typeof(IQuizManagement))] [PeerBehavior] public interface IQuizManagement {    [OperationContract(IsOneWay = true)]    void Join(Student student);    [OperationContract(IsOneWay = true)]    void Announce(string announcement); }


7.

Add these interfaces, which derive from those already defined, to the Quiz.cs? module:

public interface IQuizQuestionChannel : IQuizQuestion, IClientChannel { } public interface IQuizResponseChannel : IQuizResponse, IClientChannel { } public interface IQuizManagementChannel : IQuizManagement, IClientChannel { }


ServiceContract and OperationContract attributes have now been added to the interfaces and their methods. Those attributes signify that Windows Communication Foundation messages are to be transmitted by invoking the methods.

As explained in the preceding chapter, the CallbackContract parameter of the ServiceContract attribute designates an interface by which the receiver of a message can transmit messages back to the sender. When using Peer Channel, the CallbackContract parameter of a ServiceContract attribute must refer to the very interface to which the ServiceContract attribute applies. For instance, the CallbackContract parameter of the ServiceContract attribute on the IQuizResponse interface,

[ServiceContract(CallbackContract = typeof(IQuizResponse))] [PeerBehavior] public interface IQuizResponse {     [OperationContract(IsOneWay = true)]     void SendResponse(QuizResponse response); }


refers to the IQuizResponse interface. So to transmit messages via the IQuizResponse interface of the receiver, the sender must implement the IQuizResponse interface as well. In this particular case, the pupils' application is to send responses to quiz items to the teacher's application via the SendResponse() method of the interface, and the teacher's application is to send the teacher's evaluation of a response back to the pupil via the same method. That the OperationContract attribute on the method has its IsOneWay parameter set to TRue means that the pupils' application will not wait for a response from the teacher's application, and vice versa.

The PeerBehavior attribute on the service contract interfaces signals to the Windows Communication Foundation that the proxies it provides for communicating via that service contract must be capable of broadcasting and intercepting signals that peer proxies are coming online or going offline. For example, given the definition of IQuizResponse,

[ServiceContract(CallbackContract = typeof(IQuizResponse))] [PeerBehavior] public interface IQuizResponse {     [OperationContract(IsOneWay = true)]     void SendResponse(QuizResponse response); }


and the definition of IQuizResponseChannel,

public interface IQuizResponseChannel : IQuizResponse, IClientChannel { }


one can write a class like the one shown in Listing 11.3.

Listing 11.3. Monitoring Peers

public class QuizApplication : IQuizResponse {    public static void Main()    {        InstanceContext instanceContext =            new InstanceContext(new QuizApplication("Matt"));        using (ChannelFactory<IQuizResponseChannel> factory =            new ChannelFactory<IQuizResponseChannel>("QuizResponseEndpoint"))        {            using (IQuizResponseChannel participant =                 factory.CreateDuplexChannel(instanceContext))            {                 PeerNode node = participant.Extensions.Find<PeerNode>();                 node.Online += new EventHandler(OnOnline);                 node.Offline += new EventHandler(OnOffline);                 participant.Open();                 [...]            }        }    }    static void OnOnline(object sender, EventArgs e)    {          Console.WriteLine("Participant came online");    }    static void OnOffline(object sender, EventArgs e)    {          Console.WriteLine("Participant went offline");    } }

That class obtains a proxy called participant for communicating via the IQuizResponseChannel interface with these statements:

ChannelFactory<IQuizResponseChannel> factory =                  new ChannelFactory<IQuizResponseChannel>("QuizResponseEndpoint"); (IQuizResponseChannel participant =                  factory.CreateDuplexChannel(instanceContext);


Because IQuizResponseChannel derives from the IQuizResponse service contract, which has the PeerChannel attribute, the class can retrieve an instance of the PeerNode class from the proxy:

PeerNode node = participant.Extensions.Find<PeerNode>();


With the instance of the PeerNode class, it can receive notifications of other proxies for the IQuizResponseChannel coming online or going offline:

node.Online += new EventHandler(OnOnline); node.Offline += new EventHandler(OnOffline);


Also, the statement

participant.Open();


will broadcast notification that this proxy is online.

Unfortunately, this facility of Peer Channel is not very useful at this point in the Windows Communication Foundation's development. The Online and Offline events provided by the PeerNode class do not provide any information other than some proxy coming online or going offline. So for the applications to be used by the teacher and the pupils for the classroom quiz, the IQuizManagement interface is provided, with a Join() method that can convey information about the pupil whose application is coming online:

[ServiceContract(CallbackContract = typeof(IQuizManagement))] [PeerBehavior] public interface IQuizManagement {     [OperationContract(IsOneWay = true)]     void Join(Student student);     [OperationContract(IsOneWay = true)]     void Announce(string announcement); }


Note that, elsewhere in the Windows Communication Foundation, it is preferred to apply attributes that are behaviors to classes that implement service contract interfaces, rather than to the service contract interfaces themselves. The PeerBehavior attribute is an exception to that rule: It can be applied only to an interface.

Implementing the Communication

Usually, in building solutions using the Windows Communication Foundation, after one has defined service contracts, the interfaces that describe how messages are to be exchanged, the next step is to write classes that implement those interfaces. The same procedure applies when using Peer Channel:

  1. Add a class named QuizTeacher.cs to the Teacher application, and modify its contents to conform with Listing 11.4, which is also in the file C:\WCFHandsOn\PeerChannel\Start\Teacher\QuizTeacher1.txt.

    Listing 11.4. Implementing the Communication

    using System; using System.Collections.Generic; using System.Configuration; using System.Drawing; using System.Security.Cryptography; using System.Security.Cryptography.Xml; using System.Security.Cryptography.X509Certificates; using System.ServiceModel; using System.ServiceModel.Security.Tokens; using System.Text; namespace WindowsCommunicationFoundationHandsOn.School {     public class QuizTeacher :         IQuizQuestion,         IQuizResponse,         IQuizManagement,         IDisposable     {         private const string BaseAddressKey             = "baseAddress";         private const string QuizPortKey             = @"quizPort";         private const string MeshIdentifierKey             = @"MeshIdentifier";         private const string QuizQuestionEndpointKey             = @"quizQuestionEndpoint";         private const string QuizManagementEndpointKey             = @"quizManagementEndpoint";         private const string QuizResponseEndpointKey             = @"quizResponseEndpoint";         private const string PeerChannelAddressPrefix             = @"net.p2p://";         private const string CurrentUserCertificateStore             = @"My";         private const string CertificateSubjectName             = @"Woodgrove";         private ServiceHost resolverService = null;         private IQuizQuestion questionProxy = null;         private IQuizManagement managementProxy = null;         private int port;         private string meshIdentifier = null;         private Resolver resolver = null;         private InstanceContext site = null;         private PeerSecurityBehavior peerSecurity;         private MainForm form = null;         private Dictionary<string, IQuizResponse> responseProxies             = new Dictionary<string, IQuizResponse>();         public QuizTeacher(MainForm form)         {             this.form = form;             Uri baseAddress =                 new Uri(                     ConfigurationManager.AppSettings[                         QuizTeacher.BaseAddressKey]);             this.resolver = new Resolver();             this.resolverService = new ServiceHost(resolver, baseAddress);             this.resolverService.Open();         }         private X509Certificate2 GetCertificate()         {             X509Store store = new X509Store(                 QuizTeacher.CurrentUserCertificateStore,                 StoreLocation.LocalMachine);             store.Open(OpenFlags.ReadOnly);             X509CertificateCollection certificateCollection =                 store.Certificates.Find(                     X509FindType.FindBySubjectName,                     QuizTeacher.CertificateSubjectName,                     false);             return (X509Certificate2)certificateCollection[0];         }             #region IQuizQuestion Members             void IQuizQuestion.AddItem(QuizItem item)             {             }             #endregion             #region IQuizResponse Members             void IQuizResponse.SendResponse(QuizResponse response)             {             }             #endregion             #region IQuizManagement Members             void IQuizManagement.Join(Student student)             {             }             void IQuizManagement.Announce(string announcement)             {             }             #endregion             #region IDisposable Members             void IDisposable.Dispose()             {             }             #endregion      } }

    Now there is a class, QuizTeacher, that implements the IQuizQuestion, IQuizResponse, and IQuizManagement service contracts. The QuizTeacher class's implementations of the methods of those interfaces are currently blank.

  2. Provide content for one of the QuizTeacher's implementations of the methods of the service contracts by modifying the implementation of the SendResponse() method of the IQuizResponse interface in this way:

    void IQuizResponse.SendResponse(QuizResponse response) {     this.form.AddResponse(response); }

    This code for the teacher's application accepts a response to a quiz question sent by the pupils' application and passes it to the main form of the teacher's application for display.

  3. Switch to the MainForm.cs module, and examine the AddResponse() method, reproduced in Listing 11.5.

    Listing 11.5. Marshaling Messages onto the User Interface Thread

    public void AddResponse(QuizResponse response) {     if (this.InvokeRequired)     {         AddResponseDelegate addResponseDelegate = new AddResponseDelegate(             this.AddResponse);         this.Invoke(             addResponseDelegate,             new object[] { response });     }     else     {         lock (this)         {              response.Submitted = false;              List<QuizResponse> responses = new List<QuizResponse>(              this.quiz.Responses);              responses.Add(response);              this.quiz.Responses = responses.ToArray();              if (this.quiz.Responses.Length == 1)              {                  this.currentResponse = 0;                  this.DisplayResponse();              }              this.EnableNavigationButtons();          }     } }

    That method does the necessary work of marshaling the data received from the pupils' application onto the user interface thread so that it can be displayed.

    In Windows Communication Foundation applications, after a service contract interface has been implemented in a class, the next steps are to specify an address to which messages directed at that class can be sent, and a binding that defines how those messages must be transmitted. The procedure is no different when using Peer Channel.

  4. Provide a method to return the binding by adding the BuildBinding() method to the QuizTeacher class:

    private NetPeerTcpBinding BuildBinding(int port) {     NetPeerTcpBinding binding = new NetPeerTcpBinding();     binding.MaxMessageSize = long.MaxValue;     binding.PeerNodeAuthenticationMode = PeerAuthenticationMode.None;     binding.MessageAuthentication = false;     binding.Port = port;     return binding; }

    The Windows Communication Foundation binding that is used in building Peer Channel applications is the NetPeerTcpBinding, so the BuildBinding() method returns an instance of that binding. As the name of the binding implies, Peer Channel transmissions use TCP. Peer Channel's reliance on TCP simply reflects that of Windows Peer-to-Peer Networking.

    In making use of the NetPeerTcpBinding, one must specify the port one will be using. Hence, the BuildBinding() method accepts a port number as a parameter, and assigns that port number to the Port property of the binding:

    binding.Port = port;

    If the solution was to be deployed exclusively on machines running Windows XP Service Pack 2 and later Windows client operating systems that had Windows Peer-to-Peer Networking installed, the code for configuring the Peer Channel binding in the BuildBinding() method would be complete. As mentioned earlier, Windows Peer-to-Peer Networking provides an implementation of PNRP to resolve peer node names to network addresses. Windows Server 2003 does not support Windows Peer-to-Peer Networking and, consequently, does not have a PNRP implementation. That Windows Server 2003, which is designed to be a server operating system, is not optimized for peer-to-peer scenarios makes sense, of course. However, Peer Channel can still be used on Windows Server 2003, but to do so, one must provide one's own solution for resolving peer node names to network addresses. That is accomplished by providing a class that derives from the abstract PeerResolver class.

    The PeerResolver class is defined in this way:

    public abstract class PeerResolver {    protected PeerResolver();    public abstract object Register(        string meshId,        PeerNodeAddress nodeAddress,        TimeSpan timeout);    public abstract ReadOnlyCollection<PeerNodeAddress> Resolve(        string meshId,        int maxAddresses,        TimeSpan timeout);    public abstract void Unregister(        object registrationId,        TimeSpan timeout);    public abstract void Update(        object registrationId,        PeerNodeAddress updatedNodeAddress,        TimeSpan timeout); }

    The Register method is for adding a node to the network of peer nodes, which, in the argot of Peer Channel, is referred to as a mesh. The Resolve method returns the network addresses of the peer nodes in a given mesh.

    For the classroom quiz, the peer name resolution system works in the following way. The teacher's application incorporates a class that does peer name resolution, called Resolve. It also incorporates a class that derives from PeerResolver, called ResolverClient. ResolverClient delegates the peer name resolution work to the Resolver class. To service the peer name resolution requirements of the pupils' application, Resolver is exposed as a Windows Communication Foundation service. The pupils' application incorporates a ResolverClient class of its own that derives from PeerResolver, and that ResolverClient class invokes the methods of the remote peer name resolution service hosted by the teacher's application to do the peer name resolution work for it. This peer name resolution system is examined in detail in the next few steps.

  5. Open the IPeerResolver.cs module in the Teacher project to see the declaration of the IPeerResolver interface that describes the facilities of a remote peer name resolution service that the pupils' application can use for peer name resolution:

    public abstract class PeerResolver {     protected PeerResolver(); public abstract object Register(         string meshId,         PeerNodeAddress nodeAddress,         TimeSpan timeout);     public abstract ReadOnlyCollection<PeerNodeAddress> Resolve(         string meshId,         int maxAddresses,         TimeSpan timeout);     public abstract void Unregister(         object registrationId,         TimeSpan timeout);     public abstract void Update(         object registrationId,         PeerNodeAddress updatedNodeAddress,         TimeSpan timeout); }

  6. Next, open the ResolverService.cs module in the Teacher project, reproduced in Listing 11.6, and examine the definition of the Resolver class that implements the IPeerResolver interface and does the actual work of peer name resolution.

    Listing 11.6. Peer Name Resolution Service

    public struct Peer {     public Guid identifier;     public PeerNodeAddress nodeAddress;     public TimeSpan timeout; } [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)] public class Resolver: IPeerResolver {      private static Dictionary<string, Dictionary<Guid, Peer>> meshes          = new Dictionary<string, Dictionary<Guid, Peer>>();      public object Register(          string meshIdentifier,          PeerNodeAddress nodeAddress,          TimeSpan timeout)      {          lock (this)          {               Dictionary<Guid, Peer> mesh = null;               if (!(meshes.ContainsKey(meshIdentifier)))               {           mesh = new Dictionary<Guid, Peer>();                   meshes.Add(meshIdentifier, mesh);               }               else               {                    mesh = meshes[meshIdentifier];               }               Peer peer = new Peer();               peer.identifier = Guid.NewGuid();               peer.nodeAddress = nodeAddress;               peer.timeout = timeout;               mesh.Add(peer.identifier, peer);               return peer.identifier;          }     }     public PeerNodeAddress[] Resolve(          string meshIdentifier,          int maximumAddresses,          TimeSpan timeout)     {          List<PeerNodeAddress> addresses = new List<PeerNodeAddress>();          if (meshes.ContainsKey(meshIdentifier))          {               Dictionary<Guid, Peer> mesh = meshes[meshIdentifier];               foreach (Peer peer in mesh.Values)               {                    addresses.Add(peer.nodeAddress);                    if (addresses.Count >= maximumAddresses)                    {                         break;                    }             }       }       return addresses.ToArray(); } public void Unregister(object registrationId, TimeSpan timeout) {     lock (this)   {          foreach (Dictionary<Guid, Peer> mesh in meshes.Values)          {              if (mesh.ContainsKey((Guid)registrationId))              {                  mesh.Remove((Guid)registrationId);              }          }      } } public void Update(     object registrationId,     PeerNodeAddress updatedNodeAddress,     TimeSpan timeout) {     lock (this)     {          Peer peer;          foreach (Dictionary<Guid, Peer> mesh in meshes.Values)          {              if (mesh.ContainsKey((Guid)registrationId))              {                  peer = mesh[(Guid)registrationId];                  peer.nodeAddress = updatedNodeAddress;              }          }       }    } }

  7. Open the app.config file in the Teacher project. See that it specifies a Windows Communication Foundation address and binding for the remote peer name resolution service defined by the IPeerResolver interface:

    <?xml version="1.0" encoding="utf-8" ?> <configuration>    <appSettings>        <!-- use appSetting to configure base address provided by host -->        <add key="baseAddress"               value="net.tcp://localhost:8089/School/PeerResolverService" />        <add key="meshIdentifier" value="Classroom_3A" />        <add key="quizQuestionEndpoint" value="QuizQuestion" />        <add key="quizResponseEndpoint" value="QuizResponse" /> <add key="quizManagementEndpoint" value="QuizManagement" />        <add key="quizPort" value="8090"/>     </appSettings>     <system.serviceModel>        <services>             <service                  type="WindowsCommunicationFoundationHandsOn.School.Resolver">                  <endpoint address=""                               binding="netTcpBinding" contract="WindowsCommunicationFoundationHandsOn.School.IPeerResolver" />             </service>         </services>     </system.serviceModel> </configuration>

  8. Switch back to the QuizTeacher.cs module in the Teacher project, and look again at the constructor of the QuizTeacher class:

    public QuizTeacher(MainForm form) {     this.form = form;     Uri baseAddress =         new Uri(             ConfigurationManager.AppSettings[                 QuizTeacher.BaseAddressKey]);     this.resolver = new Resolver();     this.resolverService = new ServiceHost(resolver, baseAddress);     this.resolverService.Open(); }

    It uses the Windows Communication Foundation's ServiceHost class to provide for the peer name resolution service being hosted within the teacher's application.

  9. Now modify the BuildBinding() method, added to the QuizTeacher class earlier, so that it looks like this:

    private NetPeerTcpBinding BuildBinding(Resolver resolver, int port) {     NetPeerTcpBinding binding = new NetPeerTcpBinding();     binding.MaxMessageSize = long.MaxValue     binding.PeerNodeAuthenticationMode = PeerAuthenticationMode.None;     binding.MessageAuthentication = false;     binding.Port = port;     binding.Resolver = new ResolverClient(resolver);     return binding; }

    The modified code provides a peer name resolver for the teacher's application by assigning an instance of a class that derives from the abstract PeerResolver class to the Resolver property of the NetPeerTcpBinding.

  10. Examine the app.config file in the Child project:

    <?xml version="1.0" encoding="utf-8" ?> <configuration>      <appSettings>           <!-- use appSetting to configure base address provided by host -->           <add key="meshIdentifier" value="Classroom_3A" />           <add key="quizQuestionEndpoint" value="QuizQuestion" />           <add key="quizResponseEndpoint" value="QuizResponse" />           <add key="quizManagementEndpoint" value="QuizManagement" />       </appSettings>       <system.serviceModel>           <client>                <endpoint name="PeerResolverService" address="net.tcp://localhost:8089/School/PeerResolverService" binding="netTcpBinding" contract="WindowsCommunicationFoundationHandsOn.School.IPeerResolver, Child"/>          </client>     </system.serviceModel> </configuration>

    It configures the pupils' application as a Windows Communication Foundation client of the peer name resolution service hosted by the teacher's application.

  11. Look at the ResolverClient class in the ResolverClient.cs module of the Child project, reproduced in Listing 11.7. The class derives from the abstract PeerResolver class, and, in its implementation of that class's methods, delegates the actual work of PeerName resolution to the remote peer name resolution service hosted by the teacher's application.

    Listing 11.7. Peer Name Resolution Client

    using System; using System.Collections.Generic; using System.Collections.ObjectModel; using System.ServiceModel; using System.Text; namespace WindowsCommunicationFoundationHandsOn.School {     public class ResolverClient : PeerResolver     {         private const string PeerResolverServiceConfiguration             = "PeerResolverService"; public override object Register(             string meshId,             PeerNodeAddress nodeAddress,             TimeSpan timeout)        {             IPeerResolver proxy = null;             try             {                proxy = new ChannelFactory<IPeerResolver>(                    ResolverClient.PeerResolverServiceConfiguration).                        CreateChannel();                object registrationId =                    proxy.Register(                        meshId,                        nodeAddress,                        timeout);                return registrationId;            }            catch (Exception)            {                 return null;            }            finally            {                IChannel channel = (IChannel)proxy;                if (channel.State == CommunicationState.Opened)                {                    channel.Close();                }           }       }       public override ReadOnlyCollection<PeerNodeAddress> Resolve(           string meshId,           int maxAddresses,           TimeSpan timeout)      {           IPeerResolver proxy = null;           try           {               ReadOnlyCollection<PeerNodeAddress> addresses = null;               proxy = new ChannelFactory<IPeerResolver>(                  ResolverClient.PeerResolverServiceConfiguration).                  CreateChannel();       PeerNodeAddress[] addressArray                  = proxy.Resolve(                        meshId,                        maxAddresses,                        timeout);               addresses =                  new System.Collections.ObjectModel.                      ReadOnlyCollection<PeerNodeAddress>(                          addressArray);               return addresses;           }           catch (Exception)           {               return null;           }           finally           {               IChannel channel = (IChannel)proxy;               if (channel.State == CommunicationState.Opened)               {                   channel.Close();               }           }       }       public override void Unregister(object registrationId, TimeSpan timeout)       {            IPeerResolver proxy = null;            try            {                proxy = new ChannelFactory<IPeerResolver>(                    ResolverClient.                    PeerResolverServiceConfiguration).                    CreateChannel();                proxy.Unregister(registrationId, timeout);            }            catch (Exception)            {            }            finally            {                IChannel channel = (IChannel)proxy;                if (channel.State == CommunicationState.Opened)                {            channel.Close();                }            }        }        public override void Update(             object registrationId,             PeerNodeAddress updatedNodeAddress,             TimeSpan timeout)        {             IPeerResolver proxy = null;             try             {                 proxy =                     new ChannelFactory<IPeerResolver>(                     ResolverClient.                     PeerResolverServiceConfiguration).                     CreateChannel();                 proxy.Update(registrationId, updatedNodeAddress, timeout);             }             catch (Exception)             {             }             finally             {                  IChannel channel = (IChannel)proxy;                  if (channel.State == CommunicationState.Opened)                  {                      channel.Close();                  }              }          }      } }

  12. Open the QuizChild.cs module of the Pupil class, and locate the BuildBinding() method of the QuizChild class therein:

    private NetPeerTcpBinding BuildBinding(int port) {     NetPeerTcpBinding binding = new NetPeerTcpBinding();     binding.MaxMessageSize = long.MaxValue;     binding.PeerNodeAuthenticationMode = PeerAuthenticationMode.None; binding.Port = port;     binding.MessageAuthentication = false;     binding.Resolver = new ResolverClient();     return binding; }

    This method is the counterpart, in the pupils' application, to the BuildBinding() method that was added to the teacher's application. It instantiates an instance of Peer Channel's NetPeerTcpBinding class for the pupils' application to use in transmitting messages to the network of peers within the classroom.

  13. Switch back to the Teacher project, and the QuizTeacher.cs module once again, and modify the constructor of the QuizTeacher class so that it looks as it does in Listing 11.8.

    Listing 11.8. Defining Endpoints

    public QuizTeacher(MainForm form) {      this.form = form;      Uri baseAddress =          new Uri(              ConfigurationManager.AppSettings[                  QuizTeacher.BaseAddressKey]);      this.resolver = new Resolver();      this.resolverService = new ServiceHost(resolver, baseAddress);      this.resolverService.Open();      this.site = new InstanceContext(this);      this.port =          int.Parse(          ConfigurationManager.AppSettings[QuizTeacher.QuizPortKey]);      EndpointAddress address = null;      address =          new EndpointAddress(              @"net.p2p://Classroom_3A/QuizManagement");      NetPeerTcpBinding binding =          this.BuildBinding(this.resolver,this.port);      ChannelFactory<IQuizManagementChannel> managementChannelFactory =          new ChannelFactory<IQuizManagementChannel>(              this.site,              binding,              address); managementChannelFactory.Open();      managementProxy =          (IQuizManagementChannel)managementChannelFactory.              CreateDuplexChannel();      ((IChannel)managementProxy).Open(); }

    The additional code defines an address for messages directed at the IQuizManagement interface within the network of peers, the address net.p2p://Classroom_3A/QuizManagement. In that address, net.p2p is the scheme that signifies an address associated with a NetPeerTcpBinding binding. The next part of the address, Classroom_3A, is referred to as the mesh identifier, a mesh being, as mentioned previously, a particular network of peer nodes. The final part of the address, QuizManagement, is an arbitrary pathname chosen for the IQuizManagement endpoint.

    After the address for the IQuizManagement interface has been defined, the BuildBinding() provided earlier is used to create an instance of the NetPeerTcpBinding. Then the address and the instance of the NetPeerTcpBinding are used together with regular Windows Communication Foundation client code to open a duplex communication channel, a channel by which messages can be sent from either end.

    When the statement

    ((IChannel)managementProxy).Open();

    executes, two things will happen:

    • The managementProxy object will become available as a means to transmit messages defined by the IQuizManagement interface.

    • Any messages from nodes in the mesh directed at the IQuizManagement interface will be received by the instance of the QuizTeacher class that is being constructed.

    The latter is by virtue of the statement

    this.site = new InstanceContext(this);

    together with the subsequent reference to this.site in the statement

    ChannelFactory<IQuizManagementChannel> managementChannelFactory =      new ChannelFactory<IQuizManagementChannel>(          this.site,          binding,          address);

  14. Now that the managementProxy object is available as a means for transmitting IQuizManagement messages, add this method to the QuizTeacher class in the QuizTeacher.cs module of the QuizTeacher project:

    public void SendAnnouncement(string announcement) {      managementProxy.Announce(announcement); }

    This method uses the managementProxy object to broadcast the teacher's announcements to the instances of the pupils' application.

  15. Switch to the code view of the MainForm.cs module of the QuizTeacher project, and locate the SendButton_Click() method of the MainForm class, which serves as the handler of the Click event of the button that the teacher uses for dispatching announcements:

    private void SendButton_Click(object sender, EventArgs e) {     this.quizService.SendAnnouncement(this.AnnouncementBox.Text);     this.AnnouncementBox.Text = string.Empty; }

    It uses the SendAnnouncements() method that has just been added for broadcasting the teacher's announcements.

  16. Go to the Child project, open the QuizChild.cs module, and examine the constructor of the QuizChild class. It contains this code, which is very similar to that used to open the channel for IQuizManagement messages in the code of the teacher's application:

    //Management endpoint =     ConfigurationManager.     AppSettings[QuizChild.QuizManagementEndpointKey]; address = new EndpointAddress(     "net.p2p://Classroom_3A/QuizManagement"); ChannelFactory<IQuizManagementChannel> managementChannelFactory =     new ChannelFactory<IQuizManagementChannel>(         site,         this.BuildBinding(port),         address); managementChannelFactory.Open(); managementProxy =    (IQuizManagementChannel)managementChannelFactory.    CreateDuplexChannel(); ((IChannel)managementProxy).Open();

    This is the code by which the pupils' application can send and receive messages defined by the IQuizManagment interface and directed at the peers within the Classroom_3A mesh.

    Recall how the IQuizManagement interface is defined:

    public interface IQuizManagement {      void Join(Student student);      void Announce(string announcement); }

    Also recall that the pupils' application is to send the Join message in order to signal to the teacher that the pupil is ready to participate in the quiz, and the teacher's application is to send the Announce message to the pupils' application when the teacher has an announcement to make to the students. Now, in fact, when a pupil's application sends the Join message, Peer Channel will deliver that message to all the peer nodes in the Classroom_3A mesh listening for messages directed at the QuizManagement path. Consequently, not only will the teacher's application receive a Join message sent by a pupil's application, but so will all the other instances of the pupils' application.

  17. Open the QuizChild.cs module of the Child project and see that, although the QuizChild class implements the IQuizManagement interface, the implementation of that interfaces Join() is blank:

    public class QuizChild :     IQuizQuestion,     IQuizResponse,     IQuizManagement,     IDisposable {     /* ... */     void IQuizManagement.Join(Student student)     {     }     void IQuizManagement.Announce(string announcement)     {         this.form.DisplayAnnouncement(announcement);     } }

    So, although every instance of the pupils' application will receive the Join messages sent by every other instance of that application, the pupils' application simply ignores those messages.

  18. See how the proxy for the IResponse service contract is obtained in the constructor of the QuizChild class in the same QuizChild.cs module of the Child project:

    //Response endpoint =     ConfigurationManager.         AppSettings[QuizChild.QuizResponseEndpointKey]; address = new EndpointAddress(     string.Concat("net.p2p://Classroom_3A/QuizResponse", "_",     this.student.Name)); ChannelFactory<IQuizResponseChannel> responseChannelFactory =     new ChannelFactory<IQuizResponseChannel>(         site,         this.BuildBinding(port),         address); responseChannelFactory.Open(); responseProxy =   (IQuizResponseChannel)responseChannelFactory   .CreateDuplexChannel(); ((IChannel)responseProxy).Open();

The address given for the IQuizResponse address includes the name of the pupil. For example, if the name of the pupil is Matt, the address for the channel for IQuizResponse messages for that pupil's instance of the pupils' application will be net.p2p://Classroom_3A/QuizResponse_Matt. Then, assuming that the name Matt is unique in the class, the teacher's application will be able to send messages containing the teacher's evaluation of Matt's answers to that address, over the network of peers, and have those messages received exclusively by Matt's instance of the pupils' application.

To facilitate that on the teacher's side of the exchange, go back to the QuizTeacher.cs module of the Teacher project, and complete the implementation of the Join() method of the IQuizManagement interface as shown in Listing 11.9.

Listing 11.9. Joining a "Private" Mesh

void IQuizManagement.Join(Student student) {    if (!(this.responseProxies.ContainsKey(student.Name)))    {        string endpoint =            ConfigurationManager.            AppSettings[QuizTeacher.QuizResponseEndpointKey];        EndpointAddress address = new EndpointAddress(            string.Concat(                "net.p2p://Classroom_3A/QuizResponse", "_",          this.student.Name));            NetPeerTcpBinding binding =                this.BuildBinding(this.resolver, this.port);            ChannelFactory<IQuizResponseChannel> responseChannelFactory =               new ChannelFactory<IQuizResponseChannel>(                   this.site,                   binding,                   address);            responseChannelFactory.Open();            IQuizResponseChannel responseProxy =               (IQuizResponseChannel)responseChannelFactory.               CreateDuplexChannel();            ((IChannel)responseProxy).Open();            this.responseProxies.Add(student.Name, responseProxy);            this.form.UpdateStudents(student.Image);      } }

This code for the teacher's application receives IQuizManagement Join messages from the pupils' application. It creates a channel for sending the teacher's evaluation of a pupil's responses to the quiz questions directly to that pupil's application within the Peer Channel mesh. It does that by formulating an address incorporating the pupil's name in the same way that the pupil's application formulated an address in opening a channel to receive those messages from the teacher's application. The code then goes on to dispatch the image of the pupil, also included in the message received from the pupils' application, to the user interface for display.

Notice how the message is already structured when it arrives, so that it is possible to access the name of the pupil contained in the message by writing student.Name in the code, and to access the image of the pupil that is also contained in the message by writing student.Image. This facility is commonplace in programming with the Windows Communication Foundation, but it is novel in the context of peer-to-peer programming. Before, to send structured messages among peers, one had to resort to laborious measures, such as composing an XML document, storing that in a string, and passing the string as a parameter to the SendMessage() method of the Session class in the real-time communications client API.

See the Peer Channel Solution in Action

Follow these steps to witness Peer Channel at work:

1.

Close the solution and open another version of it, C:\WCFHandsOn\PeerChannel\Continue\PeerChannel.sln. This version of the solution simply accelerates progress by having a completed version of the QuizTeacher class in the QuizTeacher.cs of the Teacher project.

2.

Open that module and examine the implementation of the Dispose() method:

void IDisposable.Dispose() {     if (this.resolverService.State == CommunicationState.Opened)     {         this.resolverService.Close();     }     this.CloseChannel((IChannel)this.questionProxy);     this.CloseChannel((IChannel)this.managementProxy);     foreach (IQuizResponse responseProxy in this.responseProxies.Values)     {        this.CloseChannel((IChannel)responseProxy);     } }


This code simply follows good practice in closing the peer name resolution service and all the channels for peer-to-peer communication that have been opened, when the application is shutting down. This Dispose() method is called from the handler of the FormClosed event of the application's main form.

3.

Right-click on the Teacher project in the Visual Studio Solution Explorer, and choose Debug, Start New Instance from the context menu that appears. The teacher's application should appear, as shown in Figure 11.5.

Figure 11.5. Teacher's application.


4.

Right-click on the Child project in the Visual Studio Solution Explorer, and choose Debug, Start New Instance from the context menu that appears. The pupil's application should appear, as shown in Figure 11.6.

Figure 11.6. Pupils' application.


5.

Switch back to the main form of the teacher's application. At the top of that form, there should be the picture of a pupil transmitted by the pupil's application as that application started, as shown previously in Figure 11.1.

6.

Click on the Start button on the main form of the teacher's application, and switch to the main form of the pupil's application. The pupil's application should now have received a quiz question from the teacher's application. In fact, if you were to use the buttons labeled with arrows to navigate forward and backward through the quiz questions, you should find that several quiz questions have been received by the pupil's application.

7.

Select an answer for any one of the questions, and click on the OK button.

8.

Switch back to the teacher's application, and, in the Grading section, the response chosen in the pupil's application should be shown in the teacher's application.

9.

Choose whether the answer is correct, and click on the Grade button.

10.

Switch to the pupil's application, and a checkmark or a cross will have appeared to signify whether the answer to the question was judged to be correct or incorrect.

11.

Stop debugging the applications.

Securing the Peer Channel Solution

Follow these steps to secure the peer communication:

1.

Open the QuizTeacher.cs module of the Teacher project. Locate the BuildBinding() method of the QuizTeacher class, and modify that method to read as shown here:

private NetPeerTcpBinding BuildBinding(Resolver resolver, int port) {     NetPeerTcpBinding binding = new NetPeerTcpBinding();     binding.MaxMessageSize = long.MaxValue;     binding.PeerNodeAuthenticationMode =         PeerAuthenticationMode.MutualCertificate;     binding.MessageAuthentication = true;     binding.Port = port;     binding.Resolver = new ResolverClient(resolver);     return binding; }


This enables the authentication of the sources of messages within the network of peers, as well as the digital signing of the messages to ensure that any tampering with the contents of the messages en route will be detected. The mode of authentication that we have selected requires that messages be signed with the private key of an X.509 certificate, the public key of which is trusted by the recipient.

2.

Locate the definition of the ServiceValidator class in the same QuizTeacher.cs module, and modify it as shown here:

public override void Validate(X509Certificate2 certificate) {     if (certificate.SubjectName.Name != "CN=Woodgrove")     {         throw new Exception("We don't trust you!");     } }


The ServiceValidator class derives from the abstract X509CertificateValidator class. In the implementation of that abstract class' sole Validate() method, one does the work of deciding whether to accept the X.509 certificate passed as a parameter to the method.

3.

Now go to the constructor of the QuizTeacher class in the same module, and modify it as shown in Listing 11.10.



Listing 11.10. Using the PeerSecurityBehavior in the Teacher's Application

public QuizTeacher(MainForm form) {     this.form = form;     Uri baseAddress = new Uri(         ConfigurationManager.AppSettings[QuizTeacher.BaseAddressKey]);     this.resolver = new Resolver();     this.resolverService = new ServiceHost(resolver, baseAddress);     this.resolverService.Open();     site = new InstanceContext(this);     this.port = int.Parse(         ConfigurationManager.AppSettings[QuizTeacher.QuizPortKey]);     this.meshIdentifier =         ConfigurationManager.         AppSettings[QuizTeacher.MeshIdentifierKey];     string endpoint = null;     EndpointAddress address = null;     SecurityValidator validator = new SecurityValidator();     peerSecurity = new PeerSecurityBehavior();     peerSecurity.SetSelfCertificate(this.GetCertificate());     peerSecurity.SetPeerNodeX509Authentication(validator);     peerSecurity.SetMessageX509Authentication(validator);     endpoint =        ConfigurationManager.        AppSettings[QuizTeacher.QuizManagementEndpointKey];    address = new EndpointAddress(        string.Concat(            QuizTeacher.PeerChannelAddressPrefix,            this.meshIdentifier,            "/",            endpoint)); ChannelFactory<IQuizManagementChannel> managementChannelFactory =    new ChannelFactory<IQuizManagementChannel>(        this.BuildBinding(            this.resolver,            this.port),            address); managementChannelFactory.Description.Behaviors.Add(peerSecurity); managementChannelFactory.Open(); managementProxy =     (IQuizManagementChannel)managementChannelFactory. CreateDuplexChannel(           this.site);     ((IChannel)managementProxy).Open();     endpoint =        ConfigurationManager.        AppSettings[QuizTeacher.QuizQuestionEndpointKey];     address =        new EndpointAddress(            string.Concat(                QuizTeacher.PeerChannelAddressPrefix,                this.meshIdentifier,                "/",                endpoint)); ChannelFactory<IQuizQuestionChannel> questionChannelFactory =     new ChannelFactory<IQuizQuestionChannel>(         this.BuildBinding(             this.resolver,             this.port),         address); questionChannelFactory.Description.Behaviors.Add(peerSecurity); questionChannelFactory.Open(); questionProxy =      (IQuizQuestionChannel)questionChannelFactory.      CreateDuplexChannel(this.site);   ((IChannel)questionProxy).Open(); }

The newly-added code instantiates an instance of the SecurityValidator class; then it creates an instance of the PeerSecurityBehavior class, and passes the instance of the SecurityValidator class to the SetPeerNodeX509Authentication() and SetMessageX509Authentication() methods. That configures the PeerSecurityBehavior object so that, as messages arrive, the sources thereof, as well as the digital signatures certifying the integrity of the messages, are validated by the SecurityValidator class defined earlier.

The line

peerSecurity.SetSelfCertificate(this.GetCertificate());


tells the PeerSecurityBehavior which X.509 certificate our application should use to sign its messages and authenticate itself to other nodes in the peer network.

The lines

managementChannelFactory.Description.Behaviors.Add(peerSecurity);


and

questionChannelFactory.Description.Behaviors.Add(peerSecurity);


apply the PeerSecurityObject that we have configured to the channels that we use for communicating with the pupils' application.

4.

Add a similar line to the implementation of the IQuizManagement interface's Join() method in the QuizTeacher class, as shown in Listing 11.11.

Listing 11.11. Securing the "Private" Meshes

void IQuizManagement.Join(Student student) {    if (!(this.responseProxies.ContainsKey(student.Name)))    {        string endpoint =            ConfigurationManager.            AppSettings[QuizTeacher.QuizResponseEndpointKey];        EndpointAddress address =            new EndpointAddress(                string.Concat(                   QuizTeacher.PeerChannelAddressPrefix,                   this.meshIdentifier,                   "/",                   endpoint,                   "_",                   student.Name));     ChannelFactory<IQuizResponseChannel> responseChannelFactory =         new ChannelFactory<IQuizResponseChannel>(             this.BuildBinding(                   this.resolver,                   this.port),             address);    responseChannelFactory.Description.Behaviors.Add(this.peerSecurity);    responseChannelFactory.Open();    IQuizResponseChannel responseProxy =        (IQuizResponseChannel)responseChannelFactory.CreateDuplexChannel(             this.site);    ((IChannel)responseProxy).Open();    this.responseProxies.Add(student.Name, responseProxy);    this.form.UpdateStudents(student.Image);    } }

5.

Now open the QuizChild.cs module of the Child project, and modify the BuildBinding() method of the QuizChild class therein so that the security configuration of the binding used by the pupils' application matches that of the teacher's application:

private NetPeerTcpBinding BuildBinding(int port) {     NetPeerTcpBinding binding = new NetPeerTcpBinding();     binding.MaxMessageSize = long.MaxValue;     binding.PeerNodeAuthenticationMode = PeerAuthenticationMode.MutualCertificate;     binding.Port = port;     binding.MessageAuthentication = true;     binding.Resolver = new ResolverClient();     return binding; }


6.

Amend the constructor of the QuizChild class as shown in Listing 11.12.

Listing 11.12. Using the PeerSecurityBehavior in the Pupils' Application

public QuizChild(MainForm form, string[] parameters) {     this.form = form;     this.student.Name = parameters[0];     int port = int.Parse(parameters[1]);     InstanceContext site = new InstanceContext(this);     string endpoint = null;     EndpointAddress address = null;     string meshIdentifier =         ConfigurationManager.AppSettings[QuizChild.MeshIdentifierKey];     SecurityValidator validator = new SecurityValidator();     peerSecurity = new PeerSecurityBehavior();     peerSecurity.SetSelfCertificate(this.GetCertificate());     peerSecurity.SetPeerNodeX509Authentication(validator);     peerSecurity.SetMessageX509Authentication(validator);     //Management     endpoint =        ConfigurationManager.        AppSettings[QuizChild.QuizManagementEndpointKey]; address = new EndpointAddress(        string.Concat(            QuizChild.PeerChannelAddressPrefix,            meshIdentifier,            "/",            endpoint));     ChannelFactory<IQuizManagementChannel> managementChannelFactory =        new ChannelFactory<IQuizManagementChannel>(            this.BuildBinding(port),address);     managementChannelFactory.Description.Behaviors.Add(peerSecurity);     managementChannelFactory.Open();     managementProxy =        (IQuizManagementChannel)managementChannelFactory.CreateDuplexChannel(             site);    ((IChannel)managementProxy).Open();    //Question    endpoint =       ConfigurationManager.       AppSettings[QuizChild.QuizQuestionEndpointKey];    address =       new EndpointAddress(           string.Concat(               QuizChild.PeerChannelAddressPrefix,               meshIdentifier,               "/",               endpoint));    ChannelFactory<IQuizQuestionChannel> questionChannelFactory =       new ChannelFactory<IQuizQuestionChannel>(           this.BuildBinding(port),           address);    questionChannelFactory.Description.Behaviors.Add(peerSecurity);    questionChannelFactory.Open();    questionProxy =       (IQuizQuestionChannel)questionChannelFactory.CreateDuplexChannel(            site);    ((IChannel)questionProxy).Open();    //Response    endpoint =        ConfigurationManager.        AppSettings[QuizChild.QuizResponseEndpointKey];    address =        new EndpointAddress( string.Concat(           QuizChild.PeerChannelAddressPrefix,           meshIdentifier,           "/",           endpoint,           "_",           this.student.Name));    ChannelFactory<IQuizResponseChannel> responseChannelFactory =       new ChannelFactory<IQuizResponseChannel>(           this.BuildBinding(port),           address);    responseChannelFactory.Description.Behaviors.Add(peerSecurity);    responseChannelFactory.Open();    responseProxy =        (IQuizResponseChannel)responseChannelFactory.CreateDuplexChannel(site);    ((IChannel)responseProxy).Open();    //Announce student to teacher:    this.Join(); }

7.

Install the certificate used to secure the exchange of messages by executing the batch file C:\WCFHandsOn\PeerChannel\SetUp.bat. The Setup.bat batch file assumes that the tools included with the version of the Microsoft Windows SDK for use with WinFX are installed in the folder C:\Program Files\Microsoft SDKs\Windows\v1.0\Bin. If they are not installed there, modify the batch file accordingly. If their location is unknown, search the hard disks for the tool CertKeyFileTool.exe; the other tools should be in the same location. A second batch file, C:\WCFHandsOn\CleanUp.bat, is provided for removing the certificate after the exercise has been completed.

8.

Start testing the newly secured solution by right-clicking on the Teacher project in the Visual Studio Solution Explorer, and choosing Debug, Start New Instance from the context menu that appears. The teacher's application should appear.

9.

Right-click on the Child project in the Visual Studio Solution Explorer, and choose Debug, Start New Instance from the context menu that appears. The pupil's application should appear.

10.

Switch back to the main form of the teacher's application. At the top of that form, there should be the picture of a pupil transmitted by the pupil's application as that application started, confirming that the pupil's application can still transmit messages to the teacher's application.

11.

Click on the Start button on the main form of the teacher's application, and switch to the main form of the pupil's application. The pupil's application should now have received a quiz question from the teacher's application, confirming that the teacher's application can still send messages to the pupil's application.

12.

Stop debugging the applications.

Defense in Depth

The Windows Peer-to-Peer Networking infrastructure that underlies Peer Channel provides for securing the exchange of messages among peers using a shared secret, either a password or an X.509 certificate. In the foregoing steps, a shared X.509 certificate was used.

Note, however, that depending on how the binding of the peer name resolution service is configured, the participants must also authenticate themselves to that service in order to access the network of peers. In the application used in this chapter, the configuration file of the teacher's application set the binding to be the NetTcpBinding, which, by default, authenticates users by their Windows access tokens:

<?xml version="1.0" encoding="utf-8" ?> <configuration>     [...]     <system.serviceModel>         <services>             <service                  type="WindowsCommunicationFoundationHandsOn.School.Resolver">                  <endpoint address=""                               binding="netTcpBinding" contract="WindowsCommunicationFoundationHandsOn.School.IPeerResolver" />             </service>         </services>     </system.serviceModel> </configuration>


However, the authentication and authorization of users of the peer name resolution service can be configured in any of the myriad ways that are supported by the Windows Communication Foundation. That provides an important, initial layer of security for peer-to-peer communications.

Style

In the foregoing steps, Windows Communication Foundation channels were created and configured using code rather than a configuration file. If it was not necessary to add security to the channels, they might have been defined using a configuration file in this way:

<system.serviceModel>     <client>         <endpoint configurationName="PeerResolverService"              address="net.tcp://localhost:8089/School/PeerResolverService"        binding="netTcpBinding"              contract= "WindowsCommunicationFoundationHandsOn.School.School.IPeerResolver, Pupil"/>         <endpoint configurationName="QuizQuestion"              address="net.p2p://Classroom_3A/QuizQuestion"              binding="netPeerTcpBinding"              bindingConfiguration="PeerBinding"              contract= "WindowsCommunicationFoundationHandsOn.School.School.IQuizQuestion, Pupil"/>     </client>     <bindings>          <netPeerTcpBinding>               <binding configurationName="PeerBinding"                     port="8091"                     maxMessageSize="4194304"                     messageAuthentication="true"                     peerNodeAuthenticationMode="mutualCertificate"               />          </netPeerTcpBinding>     </bindings> </system.serviceModel>


Then one could simply write

ChannelFactory<IQuizQuestion> questionChannelFactory =     new ChannelFactory<IQuizQuestion>(site,"QuizQuestion"); questionChannelFactory.Open(); questionProxy = (IQuizQuestion)questionChannelFactory.CreateDuplexChannel(); ((IChannel)questionProxy).Open();


rather than

endpoint =     ConfigurationManager.     AppSettings[QuizTeacher.QuizQuestionEndpointKey]; address =     new EndpointAddress(         string.Concat(             QuizTeacher.PeerChannelAddressPrefix,             this.meshIdentifier,             "/",             endpoint)); ChannelFactory<IQuizQuestionChannel> questionChannelFactory =     new ChannelFactory<IQuizQuestionChannel>(         this.BuildBinding( this.resolver,         this.port),     address); questionChannelFactory.Description.Behaviors.Add(peerSecurity); questionChannelFactory.Open(); questionProxy =     (IQuizQuestionChannel)questionChannelFactory.     CreateDuplexChannel(this.site); (IChannel)questionProxy).Open();


However, the PeerSecurityBehavior class used in the exercise can be defined only using code, not configuration. So all the definition of the channels was done in code rather than in configuration.




Presenting Microsoft Communication Foundation. Hands-on.
Microsoft Windows Communication Foundation: Hands-on
ISBN: 0672328771
EAN: 2147483647
Year: 2006
Pages: 132

flylib.com © 2008-2017.
If you may any questions please contact us: flylib@qtcs.net