Click here to Skip to main content
Click here to Skip to main content

Peer Graph - Peers and Connections

, 17 Nov 2005
Rate this:
Please Sign up or sign in to vote.
Peer Graph - Peers and Connections using Microsoft's Peer-to-Peer technology.

Background

This article introduces the concepts of Peers and Connections in Peer-to-Peer Graphing. It is a continuation of the first article which introduces Microsoft's Peer-to-Peer Graphing technology and shows you how to create, open, close and delete a graph. Just to recap, graphing provides a stable, reliable, and robust infrastructure for Windows peer-to-peer applications to communicate. Graphs are the foundation for connecting peers, services and resources within a peer network. Peers use Peer Name Resolution Protocol (PNRP - a server less DNS) to register and discover other peers within the graph.

A graph consists of peers (sometimes referred to as nodes). A peer can be a user-interactive application, service or resource. Graphing allows data to be passed between peers efficiently and reliably.

Introduction

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#. The sample application includes a PeerGraph class that includes additional events to notify when peers connect and disconnect, connections are opened and closed, and methods to open and close a direct communication connection between two peers. While the PeerGraph class hides the details of using Microsoft's Peer-to-Peer Graphing APIs (in order to simplify the programming model), the flow of unmanaged calls is described below. The remainder of this article focuses on peer notifications and opening and closing a direct communication connection between peers.

Peer Connected and Disconnected Event

When a new peer connects to or disconnects from the graph, all other peers receive a PEER_GRAPH_EVENT_NODE_CHANGED notification. The PeerGraph class handles this notification and, in turn, fires one of three events as shown below.

private void HandleEventPeerChanged(IntPtr evptr)
{
  PEER_EVENT_NODE_CHANGE_DATA ndata = 
     (PEER_EVENT_NODE_CHANGE_DATA)Marshal.PtrToStructure(evptr, 
     typeof(PEER_EVENT_NODE_CHANGE_DATA));

  // if local node address has changed, re-register with PNRP
  if (ndata.changeType == PeerNodeAction.Updated && ndata.ullNodeId == 0)
    RegisterAddress();
  else
  {
    PeerGraphPeerChangedEventArgs args = new 
        PeerGraphPeerChangedEventArgs(ndata.ullNodeId, ndata.pwzPeerName);
    switch (ndata.changeType)
    {
    case PeerNodeAction.Connected:
      if (PeerConnected != null) PeerConnected(this, args);
      break;
    case PeerNodeAction.Disconnected:
      if (PeerDisconnected != null) PeerDisconnected(this, args);
      break;
    case PeerNodeAction.Updated:
      if (PeerUpdated != null) PeerUpdated(this, args);
      break;
    }
  }
}

Depending on the type of change, either a PeerConnected, PeerDisconnected or PeerUpdated event is fired. The ID and name of the peer is included in the event. The ID can be passed to the GetPeerInfo method of PeerGraph to get more details about the peer as shown next:

public PeerNode GetPeerInfo(System.Int64 peerId)
{
  IntPtr pNodeInfo;
  uint hr = PeerGraphNative.PeerGraphGetNodeInfo(hGraph, peerId, out pNodeInfo);
  if (hr != 0) throw new PeerGraphException(hr);

  PeerNode node = new PeerNode((PEER_NODE_INFO)Marshal.PtrToStructure(pNodeInfo, 
                                                       typeof(PEER_NODE_INFO)));
  PeerGraphNative.PeerGraphFreeData(pNodeInfo);

  return node;
}

The underlying PeerGraphGetNodeInfo API method is called which returns a PEER_NODE_INFO data structure. This data structure is marshaled and converted to a managed PeerNode class. The PeerNode class has properties for the peer's name, address and attributes. An instance of a PeerNode is used when opening a direct connection with that peer.

Opening a Direct Connection

To open a direct connection with another peer, the sample application uses the following code:

PeerNode peer = graph.GetPeerInfo(Convert.ToInt64(listBox2.SelectedItem));
connection = graph.OpenDirectConnect(peer); // step 1 - open direct connection

The selected Peer's ID is passed to GetPeerInfo to get information about its name and address. This information is passed to the graph's OpenDirectConnection method.

public System.Int64 OpenDirectConnect(PeerNode node)
{
  System.Int64 connectId;
  uint hr = PeerGraphNative.PeerGraphOpenDirectConnection(hGraph, 
            node.PeerName, ref node.addresses[0], out connectId);
  if (hr != 0) throw new PeerGraphException(hr);

  return connectId;
}

The OpenDirectMethod calls the underlying PeerGraphOpenDirectConnection API which returns a connection ID. This method is asynchronous and will result in a PEER_GRAPH_EVENT_DIRECT_CONNECTION notification to inform the application of the success or failure of the request. The PeerGraph class handles this notification and fires one of three events as shown below:

private void HandleEventConnectionChanged(IntPtr evptr, PeerConnectionType type)
{
  PEER_EVENT_CONNECTION_CHANGE_DATA ndata = (PEER_EVENT_CONNECTION_CHANGE_DATA)
      Marshal.PtrToStructure(evptr, typeof(PEER_EVENT_CONNECTION_CHANGE_DATA));

  //...
  switch (ndata.status)
  {
  case PeerConnectionStatus.Connected:
    if (ConnectionOpened != null)
    {
      foreach (PeerConnection cn in Connections)
      {
        if (cn.ConnectionId == ndata.ullConnectionId) 
        {
          PeerGraphConnectionOpenedEventArgs args = new 
             PeerGraphConnectionOpenedEventArgs(type, 
             this.OnNewDirectConnection(this, cn));
          ConnectionOpened(this, args);
          break;
        }
      }
    }
    break;
  case PeerConnectionStatus.Disconnected:
    if (ConnectionClosed != null)
    {
      PeerGraphConnectionClosedEventArgs args = new 
        PeerGraphConnectionClosedEventArgs(type, 
        ndata.ullConnectionId, ndata.ullNodeId);
      ConnectionClosed(this, args);
    }
    break;
  case PeerConnectionStatus.Failed:
    if (ConnectionFailed != null)
    {
      PeerGraphConnectionFailedEventArgs args = new 
        PeerGraphConnectionFailedEventArgs(type, ndata.ullConnectionId);
      ConnectionFailed(this, args);
    }
    break;
  }  
}

Depending on the result of opening the direction connection, either a ConnectionOpened, ConnectionClosed or ConnectionFailed event is fired. For convenience, before firing the ConnectionOpened event, the PeerGraph handler retrieves the details of the connection and passes this with the event arguments. Note that these details are not available after a disconnect or failure. On a disconnect, only the connection ID and peer ID are known. On failure, only the connection ID is known.

Connection Opened Event

The sample application overrides the ConnectionOpened event as shown below:

private void OnConnectionOpened(object sender, PeerGraphConnectionOpenedEventArgs e)
{
  System.Int64 ConnectionId = e.DirectConnection.Connection.ConnectionId;
  string PeerName = e.DirectConnection.Connection.PeerName;

  if (e.Type == PeerConnectionType.Direct)
  // step 2 - local and remote peer recieve this event
  {
    LogMessage(@"Direct", string.Format("{0} connected ({1})", 
                                     PeerName, ConnectionId));

    Connections.Add(ConnectionId, e.DirectConnection);
    listBox3.Items.Add(ConnectionId.ToString());
  }
  else
    LogMessage(@"Neighbor", 
       string.Format("{0} connected ({1})", PeerName, ConnectionId));
}

This event is received by both peers that form a direct connection. The sample application logs a message and caches the direct connection object.

Closing a Direct Connection

To close a direct connection with another peer, the sample application uses the following code:

PeerDirectConnection connection = 
  (PeerDirectConnection)Connections[Convert.ToInt64(listBox3.SelectedItem)];
if (connection != null) connection.Close();
// NOTE: cleanup handled in OnConnectionClosed

The selected connection ID is used to look for a matching cached direct connection object. If one is found, its Close method is called to close the corresponding direct connection.

Connection Closed Event

The sample application also overrides the ConnectionClosed event. Again, this event is received by both peers that formed the direction connection. When it receives this event, it simply removes the corresponding direct connection object from the cache.

private void OnConnectionClosed(object sender, 
               PeerGraphConnectionClosedEventArgs e)
{
  if (e.Type == PeerConnectionType.Direct)
  {
    System.Int64 ConnectionId = e.ConnectionId;
    LogMessage(@"Direct", 
       string.Format("{0} disconnected", ConnectionId));

    Connections.Remove(ConnectionId);
    listBox3.Items.Remove(ConnectionId.ToString());
  }
  else
    LogMessage(@"Neighbor", 
       string.Format("{0} disconnected", e.ConnectionId));
}

Using the Sample Application

The sample application lets you first create a graph (unsecured peer name 0.TestGraph) with an initial identity. The first instance should be opened with this identity. It will pause a few seconds looking for other instances, then begin to listen. Each subsequent instance of the application should open the graph with a different identity. These instances will connect to the nearest peer and synchronize. Each instance of the application is a peer.

The Peers list shows the ID of all peers connected to the graph. Click on a peer to see its properties. Double-click on a peer to open a direction connection. Note that multiple connections can be created between the same pair of peers.

The Connections list shows the ID of all connections opened to a given peer. Click on a connection to see its properties. Double-click on a connection to disconnect. Note that the Connection ID can be different for each peer with an open connection. A ConnectionId is simply a local handle resource.

The bottom list in the sample application shows a diagnostic log of all actions and incoming events. Double-click to clear the list.

Point of Interest

The PeerGraph class has some extensibility features which are not demonstrated by the sample application. It's possible to derive from the PeerGraph class and override the method OnNewDirectConnection. By default, this method creates a new PeerDirectConnection class. You can derive your own MyDirectConnection class and override the OnNewDirectConnection to create it.

Links to Resources

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

Conclusion

I hope you have found this article useful. The next article will focus on exchanging private data over a direct connection and provide enough infrastructure to build a simple chat application. Stay tuned for more articles on the following topics:

  1. Peer Name Resolution - Windows Vista Enhancements
  1. Peer Graph - Exchanging Private Data
  2. Peer Graph - Records
  3. Peer Graph - Attributes
  4. Peer Graph - Searching
  1. Peer Groups and Identity
  1. Peer Collaboration - People Near Me
  2. Peer Collaboration - EndPoints
  3. Peer Collaboration - Capabilities
  4. Peer Collaboration - Presence
  5. Peer Collaboration - Invitations

If you have suggestions for other topics, please leave a comment.

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

Share

About the Author

Adrian_Moore
Web Developer
Canada Canada
Adrian Moore is the Development Manager for the SCADA Vision system developed by ABB Inc in Calgary, Alberta.
 
He has been interested in compilers, parsers, real-time database systems and peer-to-peer solutions since the early 90's. In his spare time, he is currently working on a SQL parser for querying .NET DataSets (http://www.queryadataset.com).
 
Adrian is a Microsoft MVP for Windows Networking.

Comments and Discussions

 
GeneralMy vote of 1 PinmemberMMMaksimmm15-Aug-11 1:45 
GeneralUnknown error (0x2ced) Pinmembervasil_ek5-Jun-07 21:42 
Questionxp compatible? Pinmemberkoreaneechan9-May-07 21:30 
Generalunknow error 0*80630305 PinmemberMember #27283609-Mar-07 20:44 
GeneralRe: unknow error 0*80630305 Pinmembergchapellin12-Apr-07 5:26 
GeneralRe: unknow error 0*80630305 Pinmemberfenggui32129-Mar-10 2:25 
GeneralPeer Groups and Identity Pinmemberflopturnriver17-Jan-06 12:54 
GeneralRe: Peer Groups and Identity PinmemberAdrian_Moore17-Jan-06 16:57 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

| Advertise | Privacy | Mobile
Web04 | 2.8.140814.1 | Last Updated 17 Nov 2005
Article Copyright 2005 by Adrian_Moore
Everything else Copyright © CodeProject, 1999-2014
Terms of Service
Layout: fixed | fluid