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

Peer Graph - Records

0.00/5 (No votes)
29 Dec 2005 1  
Publishing shared data in a Peer Graph using Microsoft's Peer-to-Peer technology.

Background

Microsoft's Peer-to-Peer Graphing technology provides a stable, reliable, and robust infrastructure for Windows peer-to-peer applications to communicate. Peers use the Peer Name Resolution Protocol (PNRP - a serverless DNS) to register and discover other peers within the graph. Graphs are the foundation for connecting peers, services, and resources within a peer network. A peer can be a user-interactive application, service, or resource. Graphing allows data to be passed between peers efficiently and reliably.

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 concepts of peers publishing shared data to a peer-to-peer graph. Once a graph is opened, any peer can publish data records. Records have an expiration time. A record could contain the presence of a service, the availability of the CPU to do more work, the status of a resource that is updated periodically, or the URL of your latest blog entry. If the information about the thing being published is static or relatively small, the information can be attached to the record as data. Otherwise, the record's data should typically contain a URL or peer address to access the often changing or large chunk of information.

Any record published to the graph is automatically shared with all peers in the graph, even if they are offline. When a peer that has been offline comes online, it first synchronizes with another peer that contains all the latest records. Provided you know the record ID, you can update certain properties of an existing record. It's also possible to mark a record as deleted. The deletion eventually works its way through the graph, but will eventually be properly deleted when it expires.

This article focuses on the mechanics of adding, updating, deleting, and enumerating records in a graph. The two remaining articles in this series will discuss how attributes (meta-data) is associated with a record and searching for records by matching attributes.

Record Properties

The PeerRecord class is a wrapper for the PEER_RECORD data structure returned and consumed by the unmanaged APIs. The PeerRecord contains the following properties:

Name Description Access
ID

The GUID ID of the record generated by the unmanaged APIs.

Read-only
Type Application specific record type, for example, a chat message or a file description. Read-only
Version The version of the record generated and maintained by the unmanaged APIs. Read-only
CreatedBy The identity of the user who published the record to the graph. Read-only
ModifiedBy The identity of the user who last updated the record published to the graph. Read-only
Attributes An XML string containing meta-data describing what the record represents. Read/Write
CreationTime Time when the record was first published to the graph. Read-only
ExpirationTime The estimated time when the record will be removed from the graph. Read/Write
LastModifiedTime Time when the record was last updated and re-published to the graph. Read-only
DataAsString Any data attached to the record represented as a string. Read/Write
DataAsStream Any data attached to the record represented as a stream. Read/Write

Creating a Record

A PeerRecord can be created in three ways. The first method is used before adding and publishing a record to a graph. This method involves calling the graph's CreatePeerRecord method:

public PeerRecord CreatePeerRecord(Guid RecordType, 
                    System.TimeSpan ExpirationTime)
{
  return new PeerRecord(hGraph, RecordType, 
              DateTime.Now+ExpirationTime);
}

This method takes two parameters: the record's type, and the time span from the current time when the record will expire.

In the second method, a PeerRecord is created as the result of calling the graph's GetRecord method.

public PeerRecord GetRecord(Guid RecordId)
{
  IntPtr recptr;
  uint hr = PeerGraphNative.PeerGraphGetRecord(hGraph, 
                            ref RecordId, out recptr);
  if (hr != 0) throw new PeerGraphException(hr);

  PeerRecord record = new PeerRecord(hGraph, 
                      (PEER_RECORD)Marshal.PtrToStructure(recptr, 
                      typeof(PEER_RECORD)));
  PeerGraphNative.PeerGraphFreeData(recptr);

  return record;
}

The GetRecord method is called typically after receiving an event which indicates the change in status of a specific record. This method takes the record ID which is always supplied as part of an event notification. The GetRecord method calls the underlying PeerGraphGetRecord API.

The last method in which a PeerRecord is created is the result of enumerating the records in a graph.

Adding a Record

After calling CreatePeerRecord to create a record, set the writable properties with appropriate data and then call the graph's AddRecord method.

public Guid AddRecord(PeerRecord Record)
{
  PEER_RECORD record = Record.Convert();

  Guid recordId;
  uint hr = PeerGraphNative.PeerGraphAddRecord(hGraph, 
                            ref record, out recordId);
  if (hr != 0) throw new PeerGraphException(hr);

  return recordId;
}

The AddRecord method marshals the data and calls the underlying PeerGraphAddRecord API. If the record is successfully added, the ID of the record is returned.

Updating a Record

Records published to the graph can be updated by any application or user connected to the graph. Only the four writable properties Attributes, ExpirationTime, DataAsString, and DataAsStream can be modified. Other properties like ModifiedBy, LastModifiedTime, and Version are automatically updated by the unmanaged APIs. Use the graph's UpdateRecord method to re-publish the record to the graph.

public void UpdateRecord(PeerRecord Record)
{
  PEER_RECORD record = Record.Convert();
  uint hr = PeerGraphNative.PeerGraphUpdateRecord(hGraph, ref record);
  if (hr != 0) throw new PeerGraphException(hr);
}

public void UpdateRecord(PeerRecord Record, 
            System.TimeSpan ExpirationTime)
{
  Record.ExpirationTime += ExpirationTime;
  UpdateRecord(Record);
}

Use the first method with a single parameter when you only want to change the data or attributes that is published. This method marshals the data and calls the unmanaged PeerGraphUpdateRecord API.

Use the second method when you also want to refresh the expiration time of the record.

Deleting a Record

It's possible to delete records from the graph before they expire. For example, a file that is published to the graph is deleted locally. Use the graph's DeleteRecord method to delete an existing record published to the graph.

public void DeleteRecord(Guid RecordId)
{
  uint hr = PeerGraphNative.PeerGraphDeleteRecord(hGraph, 
                                    ref RecordId, false);
  if (hr != 0) throw new PeerGraphException(hr);
}

This method calls the underlying PeerGraphDeleteRecord API.

Record Changed Events

The PeerGraph class handles the PEER_GRAPH_EVENT_RECORD_CHANGED notification when changes in a record's status occur within the graph.

private void HandleEventRecordChanged(IntPtr evptr)
{
  PEER_EVENT_RECORD_CHANGE_DATA ndata = 
      (PEER_EVENT_RECORD_CHANGE_DATA)Marshal.PtrToStructure(evptr, 
      typeof(PEER_EVENT_RECORD_CHANGE_DATA));

  PeerGraphRecordChangedEventArgs args = new 
      PeerGraphRecordChangedEventArgs(ndata.changeType, 
      ndata.recordId, ndata.recordType);
  if (RecordChanged != null)
  {
    RecordChanged(this, args);
  }
}

The internal HandleEventRecordChanged method marshals the record ID and type and fires the RecordChanged event. There are four reasons a record notification is received: a record was added, a record was updated, a record was deleted, or a record has expired. It's only valid for an application to call the graph's GetRecord method when either an add or update notification is received. Record information is not available after a record has expired or been deleted.

Enumerating Records

The PeerGraph class provides several methods for enumerating the records published to a graph. Each method provides a different way of filtering the records being returned.

public PeerRecordCollection Records
{
  get
  {
    return new PeerRecordCollection(hGraph, Guid.Empty, string.Empty);
  }
}

public PeerRecordCollection GetRecords(Guid Type, string Identity)
{
  return new PeerRecordCollection(hGraph, Type, Identity);
}

public PeerRecordCollection GetRecords(Guid Type)
{
  return new PeerRecordCollection(hGraph, Type, string.Empty);
}

public PeerRecordCollection GetRecords(string Identity)
{
  return new PeerRecordCollection(hGraph, Guid.Empty, Identity);
}

The Records property returns all records published to the graph (no filtering). The first GetRecords method allows filtering by type and identity. The second method allows filtering by type only. The final method allows filtering by identity only. In all cases, a PeerRecordCollection class that supports the standard IEnumerable interface is returned. This class uses the unmanaged PeerGraphEnumRecords API to return and enumerate the matching records.

Using the Sample Application

The sample application lets you first create a graph (unsecured peer name 0.ChatRecords) 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 top list shows the ID of all records published to the graph. Select an item to show its properties. Double-click on a record to delete it.

The middle list shows a diagnostic log of all actions and incoming events. Double-click to clear the list.

The bottom text box allows you to enter a message. Click the Send button to publish the message as a record to the graph.

private Guid CHAT_MESSAGE_RECORD_TYPE = new 
        Guid(0x4D5B2F11, 0x6522, 0x433B, 0x84, 0xEF, 
        0xA2, 0x98, 0xE6, 0x7, 0x57, 0xB0);
private Guid recordId; // used to update existing record


private void button5_Click(object sender, System.EventArgs e)
{
  PeerRecord record;
  if (recordId == Guid.Empty)
  {
    record = graph.CreatePeerRecord(CHAT_MESSAGE_RECORD_TYPE, 
                                       new TimeSpan(0,0,10));
    record.DataAsString = textBox3.Text;
    graph.AddRecord(record);
    textBox3.Text = string.Empty;
  }
  else
  {
    record = graph.GetRecord(recordId);
    record.DataAsString = textBox3.Text;
    graph.UpdateRecord(record, new TimeSpan(0,0,3));
  }
}

If no existing record has been selected, then the entered text is associated with a new record. CreatePeerRecord is called to create a record, the text is added as data, and the record is added to the graph. Otherwise, when an existing record has been selected, the record is retrieved, the data is updated with the entered text, and the graph is updated.

The application's OnRecordChanged event is fired when the status of a record in the graph changes. When a record is added, it adds the record's ID to the top list. When a record is deleted or when it expires, it removes the record's ID from the top list. In all cases, a diagnostic message is recorded.

private void OnRecordChanged(object sender, 
             PeerGraphRecordChangedEventArgs e)
{
  PeerRecord record;
  switch (e.Action)
  {
  case PeerRecordAction.Added:
    record = graph.GetRecord(e.RecordId);
    LogMessage(@"Added", record.DataAsString);
    listBox2.Items.Add(e.RecordId.ToString());
    break;
  case PeerRecordAction.Deleted:
    LogMessage(@"Deleted", e.RecordId.ToString());
    listBox2.Items.Remove(e.RecordId.ToString());
    break;
  case PeerRecordAction.Expired:
    LogMessage(@"Expired", e.RecordId.ToString());
    listBox2.Items.Remove(e.RecordId.ToString());
    break;
  case PeerRecordAction.Updated:
    record = graph.GetRecord(e.RecordId);
    LogMessage(@"Updated", record.DataAsString);
    textBox3.Text = record.DataAsString;
    recordId = e.RecordId;
    break;
  }
}

Points of Interest

In order for the PeerRecord class to be cached by an application, it must make a copy of the unmanaged data. The reason for this is that after the PEER_RECORD has been marshaled, its memory is freed. To simplify freeing memory, Microsoft chose to create a continuous block of memory to store a record and its data. That is, the data follows the record in the same block of memory. As a result, once the memory is freed, the data is no longer accessible from managed code (the IntPtr is invalid). In an effort to maintain the data in marshalable form, the unmanaged data is copied into the unmanaged memory. It's too bad, Microsoft doesn't provide a better way to do this.

private void CopyUnmanagedData(PEER_DATA data)
{
  IntPtr ptr = Marshal.AllocHGlobal(data.cbData);
  byte[] buffer = new byte[data.cbData];
  Marshal.Copy(data.pbData, buffer, 0, data.cbData);
  Marshal.Copy(buffer, 0, ptr, data.cbData);
  data.pbData = ptr;
}

I will need to run some benchmarks to determine if there is a more optimal approach to handling this problem, especially if the majority of the time, it's only marshaled for reading.

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 associating XML meta-data with a record. Stay tuned for more articles on the following topics:

  1. Peer Name Resolution - Windows Vista Enhancements
  1. Peer Graph - Attributes
  2. Peer Graph - Searching
  3. Peer Graph - Importing and Exporting a Database
  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