Chapter 17. Peer Communication

<feature><title>In This Chapter</title> </feature>

Introducing Peer Channel

As explained in Chapter 14, “Custom Channels,” Windows Communication Foundation protocols are implemented as channels. A transport channel called Peer Channel is provided for multiparty, peer-to-peer communication.

Peer Channel is a significant innovation of the Windows Communication Foundation that provides two very important benefits. First, it enables the construction of sophisticated peer-to-peer applications involving the exchange of structured data. More generally, though, Peer Channel provides simply the easiest way to leverage the new Windows Peer-to-Peer Networking facilities available in Windows XP Service Pack 2 and later Windows client operating systems.

Using Structured Data in Peer-to-Peer Applications

Today, on Windows XP and later Windows operating systems, the Windows application programming interface (API) incorporates a real-time communications client API. That API enables one to develop applications incorporating facilities for real-time human communications in a variety of forms: instant messaging, two-way voice and video, and application sharing. The various Microsoft instant messaging solutions are built using the real-time communications client API.

Although one can certainly construct powerful and interesting applications with that API, those applications rely on the human participants to structure the data that is being exchanged. When one is doing instant messaging or conversing verbally with someone via one’s computer over a network, one relies on one’s linguistic and verbal abilities to be understood. Yet there are many circumstances in which the computer could assist considerably in structuring the data being passed around. For example, in the species of file-sharing application exemplified by the original Napster, users provide some input every now and then, but the bulk of the activity is done by the application that accepts the user’s input, puts it into a form meaningful to its peers, and then goes about getting what the user wanted. Here is another example: It is becoming increasingly common for technology companies to offer technical support via instant messaging. Users of those technical-support instant messaging applications do want to start exchanging free-form text messages with one another. However, before they reach that point, they must generally identify themselves, and describe their needs so that the appropriate support person can be engaged to assist them, which is a process wherein structured data must be gathered from them, exchanged between applications, and processed. The Windows Communication Foundation’s Peer Channel facility is the right technology to choose in any scenario like that, in which computer software applications need to exchange structured data with one another on behalf of their respective human users.

Leveraging the Windows Peer-to-Peer Networking Development Platform

Windows XP Service Pack 2 and later Windows client operating systems provide Windows Peer-to-Peer Networking as an optional Windows Networking Services component. Windows Peer-to-Peer Networking is a developer platform for building secure, scalable, and autonomic peer-to-peer applications of any kind. Its key components are these:

  • The Peer Name Resolution Protocol (PNRP) as a solution to the problem of resolving the name of network peers to network addresses in scenarios in which there is no central domain name server or in which the network addresses assigned to the peers change relatively frequently

  • Teredo as an implementation of IPv6 NAT Traversal or NAT-T, a proposed standard solution to the problem of traversing Network Address Translators (NATs)

  • Graphing, a mechanism for maintaining paths, ideally as short as possible, between every two peers in the network, and sustaining the graph as peers leave and join the network

  • Facilities for securing the network: controlling which peers are permitted to communicate, and ensuring the confidentiality and integrity of the communication

Peer Channel is the very easiest way to leverage all the capabilities of Windows Peer-to-Peer Networking to build server-less applications.

Understanding Windows Peer-to-Peer Networks

Computers communicating via Windows Peer-to-Peer Networking constitute a mesh. A mesh is a network in which

“information typically has more than one route it can take between any two end stations [which] works to provide fault tolerance—if a wire hub, switch or other component fails, data always reaches its destination by traveling along an alternate path.” (Tulloch and Tulloch 2002, 753)

This network topology is appropriate for peer-to-peer communication. In a network of servers, the servers and their network are maintained by administrators. By contrast, the nodes in a network of peers, as well as any single path among those nodes, must be assumed to be transient.

Communications in a Windows Peer-to-Peer network are via TCP/IP. If each node in the network had to maintain a TCP/IP connection with every other node in the mesh, the size of the mesh would be severely constrained by the resources of the nodes. So, instead, each node maintains a connection with only some other nodes, which in turn maintain a connection to a few others, and so on. Thus, each node can respond to a PNRP query on the name of the mesh with the physical network addresses of the nodes to which it is connected. Therefore, the topology of a network of Windows Peer-to-Peer applications is more properly described as partially meshed, where there are some redundant data paths among nodes, but not a direct link between every pair of nodes (Tulloch and Tulloch 2002, 754).

Using Peer Channel

As in the case of other software communication solutions, those involving the exchange of structured data via Windows Peer-to-Peer Networking can be described using the terms of the Windows Communication Foundation Service Model. In that language, communication is via endpoints, each of which is defined by an address, a binding, and a contract.

Endpoints

Usually, services provide endpoints with which clients can communicate. In the case of peer-to-peer applications, the applications are, by definition, peers, and so it is not appropriate to differentiate one as the service to which others are clients. Instead, in defining peer nodes in the language of the Windows Communication Foundation Service Model, one represent all the nodes as clients of one another:

<system.serviceModel>
        <client>
                <endpoint
                        name="MyPeerEndpoint"
                        [...]/>
        </client>
</system.serviceModel>

Binding

To indicate that a peer node is to use Peer Channel to communicate via Windows Peer-to-Peer Networking, one selects as the binding for the peer node endpoint the predefined NetPeerTcpBinding. As the name of the binding implies, communications via the NetPeerTcpBinding use TCP. The reliance on TCP simply reflects that of Windows Peer-to-Peer Networking.

The NetPeerTcpBinding can be selected in the configuration of an endpoint in the usual way,

<system.serviceModel>
        <client>
                <endpoint
                        name="MyPeerEndpoint"
                        binding="netPeerTcpBinding"
                      [...]/>
        </client>
</system.serviceModel>

although a port must be specified, and that is done through a customization of the binding, rather than via the address:

<system.serviceModel>
        <client>
                <endpoint
                        name="MyPeerEndpoint"
                        binding="netPeerTcpBinding"
                        bindingConfiguration="PeerBinding"
                        [...]/>
        </client>
        <bindings>
          <netPeerTcpBinding>
            <binding
              name="PeerBinding"
              port="8090"
            </binding>
          </netPeerTcpBinding>
        </bindings>
</system.serviceModel>

If it is desirable to authenticate the source of communication, that option can also be selected via a customization of the binding. The source can be authenticated using a password or an X.509 certificate. If a certificate is to be used, it is also necessary to configure a behavior, as shown in Listing 17.1. That behavior must specify the certificate to be used to identify the source of outbound communications, and also specify the store of trusted certificates to be used to authenticate the sources of inbound communications.

Example 17.1. Securing Peer Channel Communications

<system.serviceModel>
        <client>
                <endpoint
                        name="MyPeerEndpoint"
                        binding="netPeerTcpBinding"
                        bindingConfiguration="PeerBinding"
                        behaviorConfiguration="PeerEndpointBehavior"
                        [...]/>
        </client>
        <bindings>
      <netPeerTcpBinding>
        <binding
          name="PeerBinding"
          port="8090"
        </binding>
      </netPeerTcpBinding>
    </bindings>
    <behaviors>
      <endpointBehaviors>
        <behavior name="PeerEndpointBehavior">
          <clientCredentials>
            <peer>
               <certificate
                                 findValue="CN=FabrikamEnterprises"
                                 storeLocation="LocalMachine" />
               <peerAuthentication
                                 certificateValidationMode="PeerTrust"
                                 trustedStoreLocation="CurrentUser"
                                 revocationMode="NoCheck"  />
            </peer>
          </clientCredentials>
        </behavior>
      </endpointBehaviors>
    </behaviors>
</system.serviceModel>

If the solution is to be used exclusively on machines that have Windows Peer-to-Peer Networking installed, no further configuration of the binding is necessary. However, Windows Peer-to-Peer Networking is not available on the Windows Server operating systems. The primary significance of that is that there will be no implementation of PNRP to supply the physical network addresses of the nodes within a mesh. It is still possible to deploy Peer Channel solutions on Windows Server 2003, but in that case, it is necessary to have the resolution of the physical network addresses of peers done by a peer name resolver.

A peer name resolver is a class that derives from the abstract base System.ServiceModel.PeerResolver. As shown in Listing 17.2, that base class defines a handful of abstract methods. Peer Channel will invoke those methods to add the current peer node to a mesh, and to ascertain the physical addresses of the other nodes in the mesh when the current node is to send outbound messages. One identifies one’s peer name resolver, one’s implementation of System.ServiceModel.PeerResolver through a customization of the NetPeerTcpBinding configuration, as shown in Listing 17.3. There, the custom peer name resolver is identified as the class, MyResolverClass, in the MyResolverAssembly assembly.

Example 17.2. System.ServiceModel.PeerResolver

public abstract class PeerResolver
{
   public abstract bool CanShareReferrals { get; }

    public virtual void Initialize(
                EndpointAddress address,
                Binding binding,
                ClientCredentials credentials,
                PeerReferralPolicy referralPolicy);
    public abstract object Register(
                string meshId,
                PeerNodeAddress nodeAddress,
                TimeSpan timeout);
    public abstract ReadOnlyCollection<PeerNodeAddress> Resolve(
                string meshId,
                int maxAddresses,
                TimeSpan timeout);
    public abstract void Update(
                object registrationId,
                PeerNodeAddress updatedNodeAddress,
                TimeSpan timeout);
}

Example 17.3. Identifying a Custom Peer Resolver

<system.serviceModel>
  <client>
    <endpoint
      name="MyPeerEndpoint"
      binding="netPeerTcpBinding"
      bindingConfiguration="PeerBinding"
      [...] />
    <endpoint
      name="CustomPeerResolverEndpoint"
      address="net.tcp://localhost:8089/MyResolverService"
      binding="wsHttpBinding"
      contract="IMyPeerResolverContract"/>
  </client>
  <bindings>
    <netPeerTcpBinding>
      <binding
        name="PeerBinding"
        port="8090">
        <resolver mode="Custom">
          <custom
            resolverType="MyResolverClass,MyResolverAssembly"/>
        </resolver>
      </binding>
    </netPeerTcpBinding>
  </bindings>
</system.serviceModel>

How would a custom peer name resolver accomplish the tasks of adding a node to a mesh, and retrieving the addresses of the peer nodes within a mesh? Typically, it would do so by contacting a peer name resolution service—an otherwise ordinary Windows Communication Foundation service that keeps track of the peer nodes joining the mesh. So, in Listing 17.3, a client endpoint and the peer node endpoint are configured to define how the custom peer name resolver would communicate with the peer name resolution service. Notice that the binding selected for the communication between the custom peer name resolver and the peer name resolution service in that case is the predefined wsHttpBinding. It could be any binding except NetPeerTcpBinding because the communication between the custom peer name resolver and the peer name resolution service is not peer-to-peer communication, but rather the communication of a client with a service. However, an HTTP binding is selected in this case to emphasize that the peer name resolution service does not have to maintain a TCP connection with every peer node. If it did, the resources of the peer name resolution service’s host would restrict the number of peer nodes that could be included in the mesh.

Address

Addresses used for endpoints configured with the NetPeerTcpBinding must have the scheme, net.p2p:

<system.serviceModel>
        <client>
                <endpoint
                        name="MyPeerEndpoint"
                      address="net.p2p://MyMeshIdentifier/MyEndpointName"
                        binding="netPeerTcpBinding"
                      [...]/>
        </client>
</system.serviceModel>

The next segment of the address, MyMeshIdentifier, in this example, is the identifier of the mesh of peers in which the node is to participate. The remainder of the address is the address of the endpoint relative to the base address constituted by the mesh identifier.

Contract

With Peer Channel, a message sent by any node in the peer-to-peer mesh is sent asynchronously, and delivered to every other node in the mesh, including itself. Hence, the pattern for the exchange of messages among peer applications via Peer Channel is always asynchronous and bidirectional. That message exchange pattern is represented in code using the Windows Communication Foundation by defining a service contract that identifies itself as its own callback contract:

[ServiceContract(CallbackContract = typeof(IMyContract))]
public interface IMyContract
{
    [OperationContract(IsOneWay = true)]
    void OneMessage(MyDataType data);

    [OperationContract(IsOneWay = true)]
    void AnotherMessage(string otherData);
}

That location signifies that any application sending a message defined by that service contract must also be prepared to receive that message.

Implementation

Service contracts with callback contracts are referred to as duplex contracts in the language of the Windows Communication Foundation. Windows Communication Foundation clients for sending messages defined by such contracts are derived from System.ServiceModel.DuplexClientBase<T> rather than System.ServiceModel.ClientBase<T>:

public class MyClient : DuplexClientBase<IMyContract>, IMyContract
{
  [...]
}

Peer Channel in Action

The example that will be used to show Peer Channel in action is 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 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 17.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.

The teacher’s application.

Figure 17.1. The teacher’s application.

The user interface of the pupils’ application is shown in Figure 17.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.

The pupil’s application.

Figure 17.2. The pupil’s application.

Figure 17.3 depicts one possible sequence of messages that could 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 pupils 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.

One possible sequence of messages.

Figure 17.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.

To prepare for an exploration of Peer Channel as a way of implementing the classroom quiz solution, follow these steps:

  1. Copy the code associated with this chapter downloaded from http://www.cryptmaker.com/WindowsCommunicationUnleashed to the folder C:WCFHandsOn. The code is all in a folder called PeerChannel.

  2. Open the solution C:WCFHandsOnPeerChannelPeerChannel.sln. It consists of four projects. The project called Child is for building the pupils’ application, and the project called Teacher is for building the teacher’s application. The project called ResolverClient is for building the custom peer name resolver that is used by both the pupils’ application and the teacher’s. The CustomPeerResolverService project is for building a peer name resolution service that the custom peer name resolvers of both the pupils’ application and the teacher’s application will use.

  3. Follow the instructions under the heading “Installing Certificates” in Chapter 7, “Security Basics,” to install X.509 certificates. One of those certificates, the one for Fabrikam Enterprises, will be used by the peer nodes in the example to authenticate one another.

  4. The next few steps are for copying that certificate from the local machine’s personal store to the current user’s personal store and to the current user’s Trusted People store. To begin doing that, open the Microsoft Management Console by choosing Run from the Windows Start menus, and entering

    mmc
    
  5. Choose File, Add/Remove Snap-In from the Microsoft Management Console menus.

  6. Click Add on the Add/Remove Snap-In dialog that opens.

  7. Choose Certificates from the list of available standalone snap-ins presented, and click the Add button.

  8. Select Computer Account on the Certificates snap-in dialog, and click the OK button.

  9. Accept the default on the Select Computer dialog and click the Finish button.

  10. Click Add again on the Add Standalone Snap-In dialog.

  11. Choose Certificates again from the list of available standalone snap-ins, and click the Add button.

  12. Select My User Account on the Certificates snap-in dialog, and click the Finish button.

  13. Click the Close button on the Add Standalone Snap-In dialog.

  14. Click the OK button on the Add/Remove Snap-In dialog.

  15. Expand the Certificates (Local Computer) node that now appears in the left panel of the Microsoft Management Console.

  16. Expand the Personal child node of the Certificates node.

  17. Select the Certificates child node of the Personal node.

  18. Right-click on the FabrikamEnterprises certificate that should now have appeared in the right panel, and choose Copy from the context menu.

  19. Expand the Certificates - Current User node in the left panel of the Microsoft Management Console.

  20. Expand the Personal child node of the Certificates node.

  21. Right-click on that node and choose Paste from the context menu.

  22. Expand the Trusted People child node of the Certificates node.

  23. Right-click on that node and choose Paste from the context menu.

Designing the Data Structures

Because Peer Channel is properly used for the exchange of structured data between peers, the first step in constructing a Peer Channel solution is to design the data structures to be exchanged among the peers. The representation of the data to be exchanged in messages via Peer Channel is done in the same way as it usually is with the Windows Communication Foundation—preferably by designing data contracts, or alternatively by designing XML-serializable types.

To see the data structures defined for use with the classroom quiz application, examine the data contracts in the file Quiz.cs of the Teacher project, which is shared with the Child project. They are reproduced in Listing 17.4. These data contracts represent the messages to be exchanged between the teacher’s application and the students’ application.

Example 17.4. Data Contracts

[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;
        }
    }
}

[DataContract]
public struct Student
{
    [DataMember]
    public string Name;
    [DataMember]
    public byte[] Image;
}

The Student class represents the message that the pupil’s application will send to the teacher’s application to signal that the pupil has started the application. It contains the pupil’s name and photograph.

The QuizItem class 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 class 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.

Defining the Service Contracts

Look at the service contracts defined in the code from the Quiz.cs module of the Teacher project:

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

    [OperationContract(IsOneWay = true)]
    void Announce(string announcement);
}


[ServiceContract(CallbackContract = typeof(IQuizQuestion))]
public interface IQuizQuestion
{
    [OperationContract(IsOneWay = true)]
    void AddItem(QuizItem item);
}

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

These service contracts define the messages and the message exchange patterns required for the classroom quiz solution. The messages they define are

  • The Announce message, by which each pupil’s application will indicate to the teacher’s application that the pupil is ready for the quiz

  • The AddItem message by which the teacher’s application will send a quiz question to the pupil’s application

  • The SendResponse message by which the pupil’s application will send answers to the teacher’s, and the teacher’s application will send the evaluation of the answer to the pupil’s

Crucially, each service contract identifies itself as its own callback contract, which, as explained already, is a requirement of service contracts used with Peer Channel, signifying that any application sending a message defined by the service contract must also be prepared to receive that message.

Clients for sending messages defined by the service contracts are also in the Quiz.cs file. The code for those clients is shown in Listing 17.5.

Example 17.5. Client Classes

public class QuizQuestionClient :
     DuplexClientBase<IQuizQuestion>,
     IQuizQuestion
{
    public QuizQuestionClient(
                InstanceContext callbackInstance,
                string endpointConfigurationName)
        : base(
                        callbackInstance,
                        endpointConfigurationName)
    {
    }

    #region IQuizQuestion Members

    public void AddItem(QuizItem item)
    {
        base.Channel.AddItem(item);
    }

    #endregion
}

public class QuizResponseClient :
     DuplexClientBase<IQuizResponse>,
     IQuizResponse
{
    public QuizResponseClient(
                InstanceContext callbackInstance,
                Binding binding,
                EndpointAddress address)
        : base(
                        callbackInstance,
                        binding, address)
    {
    }

    #region IQuizResponse Members

    public void SendResponse(QuizResponse response)
    {
        base.Channel.SendResponse(response);
    }
    #endregion
}

public class QuizManagementClient :
     DuplexClientBase<IQuizManagement>,
     IQuizManagement
{
    public QuizManagementClient(
                InstanceContext callbackInstance,
                string endpointConfigurationName)
        : base(
                        callbackInstance,
                        endpointConfigurationName)
    {
    }

    #region IQuizManagement Members

    public void Join(Student student)
    {
        base.Channel.Join(student);
    }

    public void Announce(string announcement)
    {
        base.Channel.Announce(announcement);
    }

    #endregion
}

Implementing the Service Contracts

Usually, in building solutions using the Windows Communication Foundation, after one has defined the 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. Usually, though, it is only an application that is to function as a service that will implement a service contract. By contrast, because Peer Channel applications must also be able to receive the messages that they send, all the applications that are to exchange data with one another via Peer Channel implement all the service contracts defining the messages they are to exchange. Follow these steps to examine the implementations of the service contracts:

  1. Look at the definition of the QuizTeacher class in QuizTeacher.cs file of the Teacher project. It implements the IQuizManagement, IQuizQuestion, and IQuizResponse service contracts by which the message exchanges for the classroom quiz solution are defined:

    public class QuizTeacher :
            IQuizQuestion,
            IQuizResponse,
            IQuizManagement,
            IDisposable
    {
            [...]
    }
    
  2. Also look at the definition of the QuizChild class in the QuizChild.cs file of the Child project. It implements the same interfaces:

    public class QuizChild:
            IQuizQuestion,
            IQuizResponse,
            IQuizManagement,
            IDisposable
    {
            [...]
    }
    

Configuring the Endpoints

To see how the predefined NetPeerTcpBinding is used in the classroom quiz application, follow these steps:

  1. Examine the constructor of the QuizChild class in the QuizChild.cs file of the Child project. That is the code by which a pupil’s application prepares to announce the presence of the pupil to the teacher’s application. The code simply constructs instances of the QuizManagementClient and QuizQuestionClient classes defined in Listing 17.5. The System.ServiceModel.InstanceContext object passed to the constructors specifies the object to which inbound messages are to be directed, and in this case, that object is simply the current QuizChild object. The names QuizManagementEndpoint and QuizQuestionEndpoint simply identify the Windows Communication Foundation endpoint configurations. When the Open() method of the client object is invoked, it is readied to receive messages from its peers, as well as to send messages to them.

    this.site = new InstanceContext(this);
    
    //Management
    this.managementClient =
            new QuizManagementClient(
                    this.site,
                    "QuizManagementEndpoint");
    this.managementClient.Open();
    
    //Question
    this.questionClient =
            new QuizQuestionClient(
                    this.site,
                    "QuizQuestionEndpoint");
    this.questionClient.Open();
    
  2. Look for the client endpoint configurations in the App.config file of the client project. Taking one of them, the endpoint configuration named QuizManagementEndpoint, as an example,

    <endpoint
       name="QuizManagementEndpoint"
       address="net.p2p://Classroom_3A_ManagementMesh/QuizManagement"
       behaviorConfiguration="PeerEndpointBehavior"
       binding="netPeerTcpBinding"
       bindingConfiguration="PeerBinding"
       contract="WindowsCommunicationFoundationHandsOn.School.IQuizManagement"/>
    

it specifies the NetPeerTcpBinding as the binding of the endpoint. The address it provides, net.p2p://Classroom_3A_ManagementMesh/QuizManagement, conforms to the requirement of using the scheme, net.p2p, in the addresses of endpoints associated with the NetPeerTcpBinding. The next portion of the address, Classroom_3A_ManagementMesh, identifies the name of the Windows Peer-to-Peer Networking mesh to which messsages are to be directed and from which messages may be received. The final portion, QuizManagement, is the name of the endpoint relative to the base addresses constituted by the mesh name.

The customization specified for the predefined NetPeerTcpBinding identifies a port as well as a custom peer resolver—the ResolverClient class that is defined in the ResolverClient project. It also indicates that the pupil’s application and the teacher’s application are to authenticate one another’s users by means of a certificate.

<netPeerTcpBinding>
        <binding
                name="PeerBinding"
                port="8090"
                [...]
                <security mode="Transport">
                        <transport credentialType="Certificate"/>
                </security>
                <resolver mode="Custom">
                        <custom
                                resolverType=
"WindowsCommunicationFoundationHandsOn.School.ResolverClient,ResolverClient"/>
                </resolver>
        </binding>
</netPeerTcpBinding>

The behavior identified in the configuration of the endpoint specifies the certificate to use to identify the user, and the certificate store to use in authenticating the senders of inbound messages:

<behaviors>
  <endpointBehaviors>
    <behavior name="PeerEndpointBehavior">
      <clientCredentials>
        <peer>
          <certificate
                        findValue="CN=FabrikamEnterprises"
                        storeLocation="LocalMachine" />
          <peerAuthentication
                        certificateValidationMode="PeerTrust"
                        revocationMode="NoCheck" />
          <messageSenderAuthentication
                        certificateValidationMode="PeerTrust"
            revocationMode="NoCheck" />
        </peer>
      </clientCredentials>
    </behavior>
  </endpointBehaviors>
</behaviors>

A second endpoint is configured to define how the custom peer resolver is to locate and communicate with the peer name resolution service. An HTTP binding is selected in that case, which, of course, matches the choice of binding in the configuration of the peer name resolution service itself—a fact that can be confirmed by examining the App.config file of the custom peer resolver service project.

<endpoint
    name="CustomPeerResolverEndpoint"
    address="http://localhost:8089/School/PeerResolverService"
    binding="wsHttpBinding"
    contract="WindowsCommunicationFoundationHandsOn.School.IPeerResolver">
</endpoint>

Directing Messages to a Specific Peer

Remember that messages sent via Peer Channel are delivered to every node in the mesh. Yet, there might be cases in which one node has a message that is intended for a specific other node rather than for every node in the mesh. The closest one can come to satisfying that requirement with Peer Channel is to create a mesh consisting of those two nodes only. Then messages from either node, in being delivered to every node in the mesh, will be delivered to only two nodes—the sender and the intended recipient.

In the classroom quiz scenario, each pupil’s answers to the quiz questions are meant to be communicated only to the teacher, and the teacher’s evaluation of each pupil’s answers is meant to be communicated only to that pupil. To see how that requirement is satisfied in the classroom quiz application, follow these steps:

  1. Look again at the constructor of the QuizChild class in the QuizChild.cs file of the Teacher project. A QuizResponse object is constructed that the pupil’s application will use to send quiz responses to the teacher’s application and receive evaluations. The endpoint to be used by that object is defined in code rather than through configuration, and the address that is used identifies a mesh that is named for the pupil:

    string endpoint =
        ConfigurationManager.
        AppSettings[QuizChild.QuizResponseEndpointKey];
    EndpointAddress address =
        new EndpointAddress(
            string.Format("{0}{1}{2}/{3}",
                QuizChild.PeerChannelAddressPrefix,
                ConfigurationManager.AppSettings[
                                    QuizChild.MeshIdentifierKey],
                this.student.Name,
                endpoint));
    
    this.responseClient = new QuizResponseClient(
            this.site,
            new NetPeerTcpBinding(QuizChild.PeerBindingKey),
            address);
    ClientCredentials credentials = this.responseClient.ClientCredentials;
    credentials.Peer.SetCertificate(
        StoreLocation.LocalMachine,
        StoreName.My,
        X509FindType.FindBySubjectDistinguishedName,
        QuizChild.CertificateSubjectDistinguishedName);
    credentials.Peer.PeerAuthentication.CertificateValidationMode
            = X509CertificateValidationMode.PeerTrust;
    credentials.Peer.PeerAuthentication.TrustedStoreLocation
            = StoreLocation.CurrentUser;
    
    responseClient.Open();
    
  2. Study the code for the Join() method of the QuizTeacher class in the QuizTeacher.cs file of the Teacher project, reproduced in Listing 17.6. That is the method to which messages from the pupils’ applications are directed, announcing that a pupil is ready for the test. The teacher’s application creates an instance of the QuizResponse client class for communicating exclusively with each pupil, defining an endpoint with an address identifying a mesh named for the pupil. Thus, for each pupil participating in the quiz, the teacher’s application is joined to a mesh consisting of just the teacher’s application and the application used by that pupil. Hence, while the teacher’s application can broadcast quiz questions to all the pupils via the mesh to which the teacher’s application and the application of every pupil belongs, the teacher’s application can also communicate privately with each pupil’s application via another mesh to which only the two of them belong.

    In this case, the QuizResponse objects for communicating with each pupil individually are cached, and that is suitable where the number of private conversations is small. For larger meshes where one node is required to communicate privately with every other node, the connections to the private mesh of each of those other nodes will have to be more transient. When a node needs to send a message to one particular other node, it would have to join that other node’s private mesh, send the message, and then leave the private mesh. The same would have to happen in reverse if the other node wanted to respond.

    Example 17.6. Joining a Mesh with a Specific Other Node

    void IQuizManagement.Join(Student student)
    {
        if (!(this.responseClients.ContainsKey(student.Name)))
        {
            if(this.meshIdentifier == null)
            {
                this.meshIdentifier =
                                    ConfigurationManager.AppSettings[
                                            QuizTeacher.MeshIdentifierKey];
            }
            string endpoint =
                ConfigurationManager.
                AppSettings[QuizTeacher.QuizResponseEndpointKey];
            EndpointAddress address =
                new EndpointAddress(
                    string.Format("{0}{1}{2}/{3}",
                        QuizTeacher.PeerChannelAddressPrefix,
                        this.meshIdentifier,
                        student.Name,
                        endpoint));
           if(this.binding == null)
           {
                binding =
                                    new NetPeerTcpBinding(
                                            QuizTeacher.PeerBindingKey);
           }
    
           QuizResponseClient responseClient = new QuizResponseClient(
                           this.site,
                           this.binding,
                           address);
                   Credentials credentials = responseClient.ClientCredentials;
           credentials.Peer.SetCertificate(
               StoreLocation.LocalMachine,
               StoreName.My,
               X509FindType.FindBySubjectDistinguishedName,
               QuizTeacher.CertificateSubjecDistinguishedName);
    
           credentials.Peer.PeerAuthentication.CertificateValidationMode =
                           X509CertificateValidationMode.PeerTrust;
           credentials.Peer.PeerAuthentication.TrustedStoreLocation =
                           StoreLocation.CurrentUser;
    
           responseClient.Open();
    
           this.responseClients.Add(student.Name, responseClient);
    
           [...]
        }
    }
    

Custom Peer Name Resolution

Custom peer name resolution is necessary only on operating systems like Windows Server 2003 on which the .NET Framework 3 can be installed, but Windows Peer-to-Peer Networking is not avaiable. To see how custom peer name resolution is accomplished in the classroom quiz application, follow these steps:

  1. Examine the code for the ResolverClient class in the ResolverClient.cs file of the ResolverClient project, shown in Listing 17.7. The class implements the abstract methods of its System.ServiceModel.PeerResolver base. Peer Channel will invoke those methods to add the current node to a mesh, to retrieve the physical network addresses of the other nodes in the mesh, and to remove the node from the mesh. The ResolverClient class accomplishes those tasks by calling on a custom peer resolver service.

    Notice that the Resolve() method by which Peer Channel retrieves the physical network addresses of the other nodes in a mesh accepts a maxAddresses value as a parameter to restrict the number of addresses of other nodes of which the current node will be aware. Thus, no single node will necessarily know about every other node. More generally, the same algorithm that Windows Peer-to-Peer Networking uses to scale indefinitely large meshes across a partially meshed network is being used in this case, and no single node will be required to maintain a connection with every other node.

    Example 17.7. Custom Peer Name Resolver

    public class ResolverClient : PeerResolver
    {
        private const string endpointConfigurationName
                    = "CustomPeerResolverEndpoint";
    
        public override bool CanShareReferrals
        {
            get { return true; }
        }
    
        public override object Register(
                    string meshId,
                    PeerNodeAddress nodeAddress,
                    TimeSpan timeout)
        {
            using (ChannelFactory<ICustomPeerResolverChannel> factory
                            = new ChannelFactory<ICustomPeerResolverChannel>(
                                    endpointConfigurationName))
            {
                using (ICustomPeerResolverChannel client =
                                    factory.CreateChannel())
                {
                    foreach (IPAddress address in ipAddresses)
                      {
                        if (address.AddressFamily == AddressFamily.InterNetworkV6)
                          address.ScopeId = 0;
                      }
                    int registrationId = client.Register(meshId, nodeAddress);
                    return registrationId;
                }
            }
        }
        public override void Unregister(object registrationId, TimeSpan timeout)
        {
            using (ChannelFactory<ICustomPeerResolverChannel> factory =
                            new ChannelFactory<ICustomPeerResolverChannel>(
                              endpointConfigurationName))
            {
                using (ICustomPeerResolverChannel client = factory.CreateChannel())
                {
                    client.Unregister((int)registrationId);
                }
            }
        }
    
        [...]
    
        public override ReadOnlyCollection<PeerNodeAddress> Resolve(
                    string meshId,
                    int maxAddresses,
                    TimeSpan timeout)
        {
            PeerNodeAddress[] addresses = null;
    
            using (ChannelFactory<ICustomPeerResolverChannel> factory =
                            new ChannelFactory<ICustomPeerResolverChannel>(
                                    endpointConfigurationName))
            {
                using (ICustomPeerResolverChannel client =
                                    factory.CreateChannel())
                {
                    addresses = client.Resolve(
                                             meshId,
                                             maxAddresses);
                }
            }
    
            // If addresses couldn't be obtained, return empty collection
            if (addresses == null)
                addresses = new PeerNodeAddress[0];
    
            return new ReadOnlyCollection<PeerNodeAddress>(addresses);
        }
    
    }
    
  2. Look at the code of the peer name resolution service in the PeerResolver.cs file of the CustomPeerResolverService project. The code there is provided as a sample in the Windows SDK for the .NET Framework 3.0, and reproduced in Listing 17.8. The peer name resolution service defines a custom set of operations that a peer name resolver can use to add a node to a mesh, retrieve the physical network addresses of nodes in the mesh, and to remove a node from the mesh. The service simply maintains a hashtable of the nodes that have registered in a given mesh, and returns information from that hashtable in response to queries for the addresses of the nodes in the mesh. In this implementation, if the number of nodes in the mesh exceeds the number of addresses that a peer name resolver can accommodate, the peer name resolution service returns the addresses of a random subset of nodes. In a more robust implementation, it would be necessary for the peer name resolution service to ensure that the address of every node in the mesh has been provided to at least some of the other nodes.

    Example 17.8. Peer Name Resolution Service

    [ServiceContract([...])]
    public interface IPeerResolver
    {
        [OperationContract]
        int Register(string meshId, PeerNodeAddress nodeAddresses);
        [OperationContract]
        void Unregister(int registrationId);
        [OperationContract]
        void Update(int registrationId, PeerNodeAddress updatedNodeAddress);
        [OperationContract]
        PeerNodeAddress[] Resolve(string meshId, int maxAddresses);
    }
    
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single)]
    public class CustomPeerResolverService : IPeerResolver
    {
        [...]
    
        static Dictionary<int, Registration>
                    registrationTable = new Dictionary<int, Registration>();
        static Dictionary<string, Dictionary<int, PeerNodeAddress>> meshIdTable =
                    new Dictionary<string, Dictionary<int, PeerNodeAddress>>();
        static int nextRegistrationId = 0;
    
        [...]
    
        public int Register(string meshId, PeerNodeAddress nodeAddress)
        {
           bool newMeshId = false;
           int registrationId;
           Registration registration = new Registration(meshId, nodeAddress);
    
           lock (registrationTable)
           {
               registrationId = nextRegistrationId++;
               lock (meshIdTable)
               {
                   Dictionary<int, PeerNodeAddress> addresses;
                   if (!meshIdTable.TryGetValue(meshId, out addresses))
                   {
                       newMeshId = true;
                       addresses = new Dictionary<int, PeerNodeAddress>();
                       meshIdTable[meshId] = addresses;
                   }
                   addresses[registrationId] = nodeAddress;
    
                   registrationTable[registrationId] =
                                           new Registration(meshId, nodeAddress);
    
               }
           }
    
           return registrationId;
    }
    
    public void Unregister(int registrationId)
    {
        [...]
    
        registration = registrationTable[registrationId];
        registrationTable.Remove(registrationId);
    
        [...]
    }
    
    [...]
    
    public PeerNodeAddress[] Resolve(string meshId, int maxAddresses)
    {
        Console.WriteLine("Resolving the addresses of the mesh {0}.", meshId);
    
        [...]
           PeerNodeAddress [] copyOfAddresses;
           lock (meshIdTable)
           {
               Dictionary<int, PeerNodeAddress> addresses;
    
               if (meshIdTable.TryGetValue(meshId, out addresses))
               {
                   copyOfAddresses = new PeerNodeAddress[addresses.Count];
                   addresses.Values.CopyTo(copyOfAddresses, 0);
               }
               else
                    copyOfAddresses = new PeerNodeAddress[0];
           }
    
           if (copyOfAddresses.Length <= maxAddresses)
           {
               return copyOfAddresses;
           }
           else
           {
               List<int> indices = new List<int>(maxAddresses);
               while (indices.Count < maxAddresses)
               {
                   int listIndex =
                      this.random.Next() % copyOfAddresses.Length;
                   if (!indices.Contains(listIndex))
                      indices.Add(listIndex);
               }
               PeerNodeAddress[] randomAddresses =
                                   new PeerNodeAddress[maxAddresses];
               for (int i = 0; i < randomAddresses.Length; i++)
                   randomAddresses[i] = copyOfAddresses[indices[i]];
               return randomAddresses;
           }
        }
    
        [...]
    }
    

Seeing Peer Channel Work

Follow these steps to witness Peer Channel at work in the classroom quiz solution:

  1. Right-click on the CustomPeerResolverService project and choose Debug, Start New Instance from the context menu. A console for the CustomPeerResolverService application should open.

  2. Wait until output in the console for the CustomPeerResolverService application indicates that the custom peer resolver service is ready, and then right-click on the Teacher project and choose Debug, Start New Instance from the context menu. The teacher’s application should appear, as shown in Figure 17.4.

    Teacher’s application.

    Figure 17.4. Teacher’s application.

  3. Notice the output in the console of the CustomPeerResolverService application. It shows that as the teacher’s application started, it registered itself in the mesh, and retrieved the addresses of the other nodes in the mesh.

  4. Wait for a few moments, continuing to watch the output in the console of the CustomPeerResolverService application. Soon, the teacher’s application will be seen querying the addresses of the other nodes in the mesh once again as Peer Channel keeps its record of the other nodes in the mesh up to date.

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

    Pupils’ application.

    Figure 17.5. Pupils’ application.

  6. Look again at the output in the console of the CustomPeerResolverService application. As the pupil’s application started, it registered itself in the mesh, and retrieved the addresses of the other nodes in the mesh.

  7. 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 17.1.

  8. 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 one were to use the buttons labeled with arrows to navigate forward and backward through the quiz questions, one should find that several quiz questions have been received by the pupil’s application.

  9. Examine the output in the console of the CustomPeerResolverService application. When the teacher’s application sent the quiz questions to the other nodes in the mesh, there was no exchange with the peer name resolution service. That indicates that when a peer channel node sends a message, the message is propagated among the nodes via the connections that each node already has with some of the other nodes in the mesh. The nodes do not query for the addresses in the mesh each time they send a message, and the messages themselves do not go via the peer name resolution service.

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

  11. 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.

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

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

  14. Close the teacher’s application.

  15. Look at the output in the console of the CustomPeerResolverService application. As the only two nodes in the mesh, the pupil’s application and the teacher’s application were maintaining a TCP connection between themselves. When the teacher’s application shut down, Peer Channel, within the pupil’s application, detected the loss of that connection, and requested an update of the nodes currently in the mesh from the peer name resolution service.

  16. Choose Debug, Stop Debugging from the Visual Studio 2005 menus.

Peer Channel and People Near Me

People Near Me is the name of a new technology incorporated in the Windows Vista operating system (Smith 2006) that provides a user-friendly way of managing Windows Peer-to-Peer Networking meshes. So, users of the Vista operating system will be able to invite one another to participate in a mesh via an ad hoc peer-to-peer network. The technology incorporates an unmanaged API by which applications can register themselves to exchange data with other nodes in meshes created using People Near Me.

Summary

The Windows Communication Foundation’s Peer Channel makes it easy to use the facilities of the Windows Peer-to-Peer Networking infrastructure to construct applications involving the exchange of structured data among peers. Peer Channel applications, like any Windows Communication Foundation application, are defined by an address, a binding, and a contract. The addresses must have net.p2p as the scheme. The binding is the NetPeerTcpBinding. The contracts must be defined with themselves as their callback contracts. On Windows XP SP2, and other operating systems with the Windows Peer-to-Peer Networking infrastructure, peer name resolution can be left to that infrastructure. On other operating systems, such as Windows Server 2003, it is necessary to provide a custom peer name resolver that will typically work together with a peer name resolution service.

References

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset