Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Peer Group - Create, Open, and Delete

0.00/5 (No votes)
9 Feb 2006 1  
Creating, opening and deleting Peer Groups using Microsoft's Peer-to-Peer technology.

Background

Microsoft's Peer-to-Peer Grouping technology provides a stable, reliable, and robust infrastructure for Windows peer-to-peer applications to communicate. The Grouping API is based on the Graphing API, but adds a robust security system. Peers use the Peer Name Resolution Protocol (PNRP - a serverless DNS) to register and discover other peers within a group. Groups provide a secure mechanism for connecting peers, services, and resources within a peer network. Grouping allows data to be passed between peers efficiently, reliably, and securely.

Microsoft's entire Peer-to-Peer technology is exposed through the latest Platform SDK as C/C++ API calls. However, the code in this article shows these APIs being used from .NET managed code using C#.

Introduction

This article introduces the PeerGroup managed class which is the main wrapper for all the peer-to-peer grouping APIs. This article builds on the previous article which introduces peer-to-peer groups and describes details about identities.

Startup

Before any other peer-to-peer grouping APIs can be called, PeerGroupStartup must be called to initialize the group. Likewise, PeerGroupShutdown must be called before the application exits, to clean up its existence in the group. To ensure this occurs, a static singleton class is provided. The PeerGroup class includes a reference to guarantee the singleton is created before any other grouping APIs are used. As a result, you don't have to remember to do this. The following code shows the singleton class:

sealed class PeerGroupService
{
  private PeerGroupService()
  {
    Peer.Graph.PEER_VERSION_DATA data;
    uint hr = PeerGroupNative.PeerGroupStartup(1, out data);
    if (hr != 0) throw new PeerGroupException(hr);
  }
  ~PeerGroupService()
  {
    PeerGroupNative.PeerGroupShutdown();
  }
  public static readonly PeerGroupService Instance = new PeerGroupService();
}

The PeerGroup Class

The purpose of the PeerGroup class is to expose all the peer grouping functionality. However, the code download at the beginning of this article only implements the ability to create, open, and delete a group. It also provides event handlers for some of the event notifications supported by peer-to-peer groups. Future articles will expand the capabilities of this class to expose the entire peer-to-peer grouping API set.

Creating a Group

The first step to working with a group is to create one. A group only needs to be created once by the first peer to publish it. The group is owned by the identity who creates it and is the initial administrator of the group.

The static Create method takes three parameters to create a group; an identity, a group name, and a friendly name. The identity is created by a previous call to the PeerIdentity Create method. The group name is a short name for the group, for example, My Games or Kids Pictures. The friendly name provides additional information about a group such as a description or the e-mail contact of the group creator.

The following code shows how the Create method wraps the underlying PeerGroupCreate API:

public static void Create(string GroupName, 
                   PeerIdentity Creator, string FriendlyName)
{
  PeerGroupProperties properties = new PeerGroupProperties(null, 
                                   GroupName, Creator.Identity);
  properties.FriendlyName = FriendlyName;

  PEER_GROUP_PROPERTIES props = properties.Convert();
  IntPtr hGroup;
  uint hr = PeerGroupNative.PeerGroupCreate(ref props, out hGroup);
  if (hr != 0) throw new PeerGroupException(hr);

  hr = PeerGroupNative.PeerGroupClose(hGroup);
  if (hr != 0) throw new PeerGroupException(hr);
}

Behind the scenes, a graphing database is created in the "Documents and Settings\<User Name>\Application Data\PeerNetworking" folder. A sub-folder matching the identity (for example, 47e322b5473094981ad20777bed3fe71d009ec8a.User1) contains sub-folders (for example, 7cb01116ac4b9fa469ef2bdf808a662a40fdb78e.Test Group) with a database for each group that this identity creates. The database name is always called "grouping".

Accessing Groups

Groups can only be accessed by enumerating the groups associated with an identity. Accessing the Groups property of a PeerIdentity returns a PeerGroupCollection. Enumerate this collection to discover all of the PeerGroup instances currently associated with an identity. Once a PeerGroup instance is selected, its methods and properties can be accessed.

foreach (PeerGroup group in Identity.Groups)
{
  if (group.GroupName == "Test Group")
    group.Open();
  else
    group.Delete();
}

Group Properties

Internally, a PeerGroupProperties class maintains the current information about the group. This class wraps the marshaled PEER_GROUP_PROPERTIES data structure and exposes properties for each field in the structure, as shown in the following code:

public class PeerGroupProperties
{
  private PeerGroup group;
  private PEER_GROUP_PROPERTIES properties;

  internal PeerGroupProperties(PeerGroup Group, 
           string GroupName, string Identity)
  {
    group = Group;
    properties = new PEER_GROUP_PROPERTIES();
    properties.dwSize = 
      Marshal.SizeOf(typeof(PEER_GROUP_PROPERTIES));
    properties.pwzClassifier = GroupName;
    properties.pwzCreatorPeerName = Identity;
    properties.ulMemberDataLifetime = 2419200;
    properties.ulPresenceLifetime = 300;
  }

  public PeerGroupPropertyAction Action
  {
    get { return properties.dwFlags; }
    set { properties.dwFlags = value; }
  }

  public string Cloud
  {
    get { return properties.pwzCloud; }
  }

  public string GroupName
  {
    get { return properties.pwzClassifier; }
  }

  internal string InternalGroupName
  {
    get { return properties.pwzGroupPeerName; }
    set { properties.pwzGroupPeerName = value; }
  }

  public string Creator
  {
    get { return properties.pwzCreatorPeerName; }
  }

  public string FriendlyName
  {
    get { return properties.pwzFriendlyName; }
    set { properties.pwzFriendlyName = value; Update(); }
  }

  public string Comment
  {
    get { return properties.pwzComment; }
    set { properties.pwzComment = value; Update(); }
  }

  public int MemberDataLifetime
  {
    get { return properties.ulMemberDataLifetime; }
  }

  public int PresenceLifetime
  {
    get { return properties.ulPresenceLifetime; }
  }

  internal void Update()
  {
    if (group != null && group.hGroup != IntPtr.Zero)
    {
      uint hr = PeerGroupNative.PeerGroupSetProperties(group.hGroup, ref properties);
      if (hr != 0) throw new PeerGroupException(hr);
    }
  }
}

For the most part, the default values are all you will ever need. Any writable properties result in a call to the unmanaged PeerGroupSetProperties API to update the underlying property in the group.

These properties can also be retrieved after opening a group, by accessing the Properties property. This property uses the unmanaged PeerGroupGetProperties API to get the current group properties as shown in the following code:

public PeerGroupProperties Properties
{
  get
  {
    if (hGroup != IntPtr.Zero)
    {
      IntPtr ppGroupProperties;
      uint hr = PeerGroupNative.PeerGroupGetProperties(hGroup, 
                                       out ppGroupProperties);
      if (hr != 0) throw new PeerGroupException(hr);

      properties = new PeerGroupProperties(this, 
                      (PEER_GROUP_PROPERTIES)Marshal.PtrToStructure(
                      ppGroupProperties,typeof(PEER_GROUP_PROPERTIES)));
    }
    return properties;
  }
}

Deleting a Group

The Delete method of the PeerGroup class allows you to delete the peer-to-peer group created by the owning identity. The underlying PeerGroupDelete API is called as shown in the code below:

public void Delete()
{
  if (hGroup != IntPtr.Zero) Close();

  uint hr = PeerGroupNative.PeerGroupDelete(
            owner.Identity, properties.InternalGroupName);
  if (hr != 0)
      throw new PeerGroupException(hr);
}

Opening a Group

The Open method of the PeerGroup class opens and connects to the group. First, the underlying PeerGroupOpen API is called to open the group. This method passes the identity of the owner, the internal group name, and NULL (to indicate the Global_ cloud). Next, a call to register for event notifications is made. Finally, the underlying PeerGroupConnect API method is called which searches the PNRP for other peers in the group and registers the secured peer name with the PNRP.

public void Open()
{
  if (hGroup != IntPtr.Zero) Close();

  uint hr = PeerGroupNative.PeerGroupOpen(owner.Identity, 
            properties.InternalGroupName, 
            IntPtr.Zero, out hGroup);
  if (hr != 0)
      throw new PeerGroupException(hr);

  RegisterForEvents();
  hr = PeerGroupNative.PeerGroupConnect(hGroup);
  if (hr != 0) throw new PeerGroupException(hr);
}

Events

Before connecting, the wrapper registers a callback for event notifications which allows the application to receive events when something changes in the group. The following code shows how the internal RegisterForEvents method uses the PeerGroupRegisterEvent method to register for events:

private void RegisterForEvents()
{
  if (hPeerEvent != IntPtr.Zero) return; // already registered


  AutoResetEvent sendEvent = new AutoResetEvent(false);
  Handle = ThreadPool.RegisterWaitForSingleObject(sendEvent, 
           new WaitOrTimerCallback(PeerEventWorker), null, -1, false);

  PEER_GROUP_EVENT_REGISTRATION[] info = 
  {
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_STATUS_CHANGED),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_PROPERTY_CHANGED),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_RECORD_CHANGED),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_DIRECT_CONNECTION),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_NEIGHBOR_CONNECTION),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_INCOMING_DATA),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_MEMBER_CHANGED),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_CONNECTION_FAILED)
  };

  int size = Marshal.SizeOf(info[0]);
  IntPtr infoptr = Marshal.AllocCoTaskMem(info.Length*size);

  int offset = 0;
  foreach (PEER_GROUP_EVENT_REGISTRATION item in info)
  {
    Marshal.StructureToPtr(item, 
            (IntPtr)(infoptr.ToInt32()+offset), false);
    offset += size;
  }
  uint result = PeerGroupNative.PeerGroupRegisterEvent(hGroup, 
                sendEvent.Handle, info.Length, infoptr, out hPeerEvent);
  if (result != 0)
      Marshal.ThrowExceptionForHR((int)result);
}

The PEER_GROUP_EVENT_REGISTRATION data structure includes a field indicating the type of event to monitor. An array of these data structures can be passed to the register method. Much of the code above takes care of marshalling this data structure into a block of unmanaged memory.

The WaitOrTimerCallback represents a callback method that is executed when something changes in the group. RegisterWaitForSingleObject registers this delegate with the default thread pool using an infinite timeout. Finally, the event handle and data structure is passed to PeerGroupRegisterEvent.

The following code shows how the worker thread handles incoming events:

private void PeerEventWorker(object xdata, bool timedOut)
{
  while (true)
  {
    IntPtr evptr;
    uint result = PeerGroupNative.PeerGroupGetEventData(hPeerEvent, 
                                                        out evptr);
    if (result == Peer.Graph.PeerGraphNative.PEER_S_NO_EVENT_DATA 
                                          || evptr == IntPtr.Zero)
        break;
    if (result != 0) Marshal.ThrowExceptionForHR((int)result);

    PEER_GROUP_EVENT_DATA data = (PEER_GROUP_EVENT_DATA)
                                  Marshal.PtrToStructure(evptr, 
                                  typeof(PEER_GROUP_EVENT_DATA));
    IntPtr dataptr = (IntPtr)(evptr.ToInt32() + 
                      Marshal.SizeOf(typeof(PEER_GROUP_EVENT_DATA)));

    switch (data.eventType)
    {
      case PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_STATUS_CHANGED:
      HandleEventStatusChanged(dataptr);
      break;
    //...

    }
    PeerGroupNative.PeerFreeData(evptr);
  }
}

The PeerGroupGetEventData method is called to get each event. The loop ensures that all events are proceeded until PEER_S_NO_EVENT_DATA is returned indicating no further events. Each event data structure is marshaled and a switch statement is used to handle each event type.

Event Types

The following list summarizes the event types exposed by the PeerGroup class and describes when each event is fired:

StatusChanged  -  indicates the peer's status within the group (listening, has connections)
PropertyChanged  -  one or more properties of the group has changed
RecordChanged  -  data published to the graph has changed
ConnectionChanged  -  a connection to a remote peer or direction connection has changed (connected, disconnected, failed)
IncomingData  -  private data has been received from a direct connection with another peer
MemberChanged  -  a member's status within the group has changed (connected, disconnected, joined, updated)

The current sample application only implements the StatusChanged event. Future articles will demonstrate how the other events are typically used.

Using the Sample Application

The sample application allows you to select an existing identity and create, open, close, and delete peer-to-peer groups.

The Identities tab shown above allows you to view existing identities. To create an identity, enter a name and click the Create button. To delete an identity, select the identity in the list and click the Delete button.

The Create tab allows you to create a group that is associated with the selected identity. Enter a group name and a friendly name (can be blank), then click the Create button to create a group.

Switch to the Groups tab to see a list of the groups to which the identity belongs. Select a group to see its properties. Click the Open button to open and connect to the group. Once opened, any changes to the Comment or FriendlyName properties are immediately updated. Click the Close button to close the selected group. Click the Delete button to delete a group. Note that the demo does not keep track of which groups are opened and closed when selecting different groups in the list.

Point of Interest

netsh

Use the netsh command to discover another way at looking at what's going on behind the scenes when opening groups. Using netsh, switch to the 'p2p pnrp cloud' context. You will notice PNRP entries are added to the Global_ cloud after opening a group. Use the 'show names' to see the register names. The following is an example of the output you should see:

P2P Name: 8264f208a7cddc30933f3e0b0c7ea2e143e6953e.participant
Identity: 47e322b5473094981ad20777bed3fe71d009ec8a.User1
Comment:
PNRP ID:  7e154375ac617a5e2c1d86cb5ab45793.3ffe831f400419520f2185acc317053e
State:    OK
IP Addresses: [3ffe:831f:4004:1952:8000:5739:bb6f:38ea]:3587 tcp

Switch to the 'p2p idmgr' context and use the 'show groups ALL' command to see all the configured groups for the identity. The following is an example of the output you should see:

Identity P2PID = d3d9e1b0ec8accfe3fc55a0e68ef2f6a
      Peername = 47e322b5473094981ad20777bed3fe71d009ec8a.User1

   Group P2PID = 3d0e6725c882cea35327d7b533ebaf45 GMC chain len=2
      Peername = 8264f208a7cddc30933f3e0b0c7ea2e143e6953e.Test Group
      Friendly = Hi there!
  Valid: Start = 2/4/2006 13:15:32 End =2/4/3006 14:15:32 (Valid)

Switch to the 'p2p group' context and use the 'show address <Group P2PID>' command to see the address currently bound to the group. In the above case, 'show address 3d0e6725c882cea35327d7b533ebaf45' results in the following output:

Resolving participant node...
Found a participant node.
Participant node is listening on following addresses:
    [3ffe:831f:4004:1952:8000:5739:bb6f:38ea]:3587

Extensibility

A real application that uses peer-to-peer groups to share files or provide a threaded discussion must derive a custom class from the PeerGroup class. By default, the Groups property of the PeerIdentity class returns a PeerGroupCollection class which uses a PeerGroupFactory class to create the PeerGroup class. However, setting the Factory property of the PeerIdentity class allows you to provide your own factory to create your own derived, custom class.

identity.Factory = new MyPeerGroupFactory();

public class MyPeerGroup : PeerGroup
{
}

public class MyPeerGroupFactory : PeerGroupFactory
{
  public override PeerGroup CreatePeerGroup(PeerIdentity Owner)
  {
    return new MyPeerGroup(Owner);
  }
}

Future articles will show extensibility points that allow custom classes for connections, peers, records, and searching similar to those provided for the PeerGraph class.

Troubleshooting

When creating an identity or group, it's possible you might get a 'No Key Access' error. The following knowledge base article helps to explain what this means and how to work around the problem: Peer-to-Peer Framework APIs Return a "PEER_E_NO_KEY_ACCESS" Error Message.

Links to Resources

I have found the following resources to be very useful in understanding peer groups:

Conclusion

I will be covering more of the grouping APIs in future articles. Stay tuned for the next article which will describe creating invitations to invite other identities to a group and how these invitations are used by a peer to join a group.

  1. Peer Group - Invitations and Joining
  1. Peer Collaboration - People Near Me
  2. Peer Collaboration - EndPoints
  3. Peer Collaboration - Capabilities
  4. Peer Collaboration - Presence
  5. Peer Collaboration - Invitations
  1. Peer Name Resolution - Windows Vista Enhancements

If you have suggestions for other topics, please leave a comment. Finally, if you've read this far, don't forget to vote!

History

  • Initial revision.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here