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 concept of searching meta-data within peer records published to a peer-to-peer graph. As we learned in the previous article, meta-data describing the contents or purpose of the object being published is attached to each record. Microsoft refers to this meta-data as attributes. Attributes are represented as well-formed XML fragments.
In their wisdom, Microsoft again decided to use XML to represent criteria used for issuing search requests. Microsoft provides a simple schema against which the XML search criteria fragments must conform. This article focuses on the PeerSearch
class which wraps the basic search functionality supported by the unmanaged APIs.
Search Criteria Schema
The search criteria schema is very simple. The root element must be called <peersearch>
. It supports two types of operations: <and>
and <or>
. Operators can be nested and contain a <clause>
. Each <clause>
has three XML attributes: name, type and comparison. The name
attribute represents the name of the attribute, and type
its data type. Only three data types are supported: int
, date
, and string
. Six types of comparisons are supported; equal, greater-than, less-than, not-equal, greater-than-or-equal, and less-than-or-equal. The clause's value is specified between the beginning and ending tags. Here is a simple example:
<peersearch>
<or>
<clause attrib="peercreatorid"
type="string"
compare="equal">James *</clause>
<and>
<clause attrib="peerlastmodificationtime"
type="date"
compare="greater">2003-01-31</clause>
<clause attrib="peerlastmodificationtime"
type="date"
compare="less">2003-02-28</clause>
</and>
</or>
</peersearch>
Simple pattern matching is supported using *
and ?
. These characters can be escaped using the \ character.
The value of an attribute uses a case-insensitive comparison against the fields in the meta-data. That is, attrib="peercreatorid" matches against the meta-data <attribute name="PeerCreatorID" ...>. The value of each clause is also case-insensitive. That is, <clause ...>James *</clause> matches the meta-data <attribute>james bond</attribute> and <attribute>JAMES BROWN</attribute>.
The values of the type
and compare
attributes are case-sensitive since these come from the XML schema. When a date type is expected, you must use the standard XML format for date and time.
The underlying peer graphing API reserves the following list of attributes.
peerlastmodifiedby
peercreatorid
peerlastmodificationtime
peerrecordid
peerrecordtype
peercreationtime
peerlastmodificationtime
While a record's meta-data won't contain these attributes, these names can still be used in the search criteria to match the corresponding properties of a PEER_RECORD
object.
The SearchCriteria.xsd file included in the sample code contains the schema for the XML search criteria. It's copied from the MSDN library web page that describes the Record Search Query Format. This schema is used by the Validate
method described below.
Creating a Search Object
A new CreateSearch
method has been added to the PeerGraph
class that supports creating a new search object. By default, an instance of a new PeerSearch
class is returned. You can override the virtual OnCreateSearch
method to return your own, better search object since the default one is very basic.
public PeerSearch CreateSearch()
{
return OnCreateSearch();
}
protected virtual PeerSearch OnCreateSearch()
{
return new PeerSearch(this);
}
The PeerSearch
constructor takes a single Graph parameter that is the PeerGraph
against which the search will be performed.
PeerSearch Class
The PeerSearch
class includes an Xml
property for setting the XML search criteria fragment to use for searching. This class also overrides the ToString
method to return the XML search criteria fragment. The two remaining methods are described in the following sections.
Search Method
The Search
method is virtual
so it can be overridden. Its default implementation calls an internal
method of PeerGraph to perform the search.
public virtual PeerRecordCollection Search()
{
return Graph.Search(xml);
}
The internal
PeerGraph
method calls the underlying PeerGraphSearchRecords
API. If the search is valid, the results are returned in a PeerRecordCollection
collection. This collection supports the standard foreach
enumeration.
internal PeerRecordCollection Search(string Criteria)
{
IntPtr hPeerEnum;
uint hr = PeerGraphNative.PeerGraphSearchRecords(hGraph,
Criteria, out hPeerEnum);
if (hr != 0) throw new PeerGraphException(hr);
return new PeerRecordCollection(hGraph, hPeerEnum);
}
Validate Method
The sample file SearchCriteria.xsd contains the schema for validating XML search criteria fragments, and is stored as an embedded resource in the sample application. This schema is loaded and compiled as a static
(shared) field of the PeerSearch
class. The Validate
method uses the schema to validate the currently stored XML fragment.
public bool Validate()
{
XmlValidatingReader vr = new XmlValidatingReader(Xml,
XmlNodeType.Element, null);
vr.Schemas.Add(schema,null);
bool valid;
try
{
while (vr.Read()) { }
valid = true;
}
catch (XmlSchemaException ex)
{
valid = false;
}
catch (XmlException ex)
{
valid = false;
}
return valid;
}
The Validate
method returns true
if the XML fragment matches the schema, otherwise, false
if an exception is thrown. There should never be a need to validate the XML fragment, but this feature is included for completeness.
Using the Sample Application
The sample application lets you first create a graph (unsecured peer name 0.SearchSharedFiles
) 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.
On the Share tab, use the Add Folder button to select a folder containing files. After selecting a folder, each file in the folder is published to the graph as a record. Note that the record is set to expire after 1 minute. The attributes of the record are set to the properties of the FileInfo
class. Click on a file name in the list box to show the XML attributes associated with the record in the right text box. The lower list shows a diagnostic log of all actions and incoming events. Double-click to clear the list.
On the Search tab, enter an XML search criteria fragment. The source code and the demo include a file Search Criteria XML Examples.txt which contains some convenient XML search criteria fragments for you to copy and paste into the sample application. Click the Search button to issue the search.
private void Search_Click(object sender, System.EventArgs e)
{
if (textBox4.Text.Trim() == string.Empty) return;
PeerSearch search = graph.CreateSearch();
search.Xml = textBox4.Text;
try
{
PeerRecordCollection records = search.Search();
listBox3.Items.Clear();
propertyGrid1.SelectedObject = null;
foreach (PeerRecord record in records)
{
listBox3.Items.Add(new FileItem(record.DataAsString, record.ID));
}
}
catch (Exception ex)
{
System.Windows.Forms.MessageBox.Show(ex.Message, "Search Failed");
}
}
If an error occurs, a popup dialog shows the error code. Otherwise, the bottom-left list is populated with the names of the files that match the search criteria. Click on this list to show the properties of the record in a property grid.
Points of Interest
In typical P2P file sharing applications, shared files are a local resource. Information about which files you are sharing is not broadcast to every peer in the network. As a result, only through remote search requests can another peer determine if you have content that matches the search criteria. The benefit of this approach is that it scales well.
In contrast, the sample application publishes information about shared files to every peer in the graph. On startup, each peer synchronizes with the graph. Once the synchronization is complete, each peer has a complete copy of all the records in the graph. As a result, searching only needs to be performed locally. However, this approach does not scale well.
A better approach would be to have two graphs. The first graph is local to the computer. No other peer synchronizes with it, but it does contain all the files to be shared. The second graph connects to the Internet, and peers use this graph to publish search requests with a very short expiration time. On receiving this request, a peer performs the search against the local graph. Any results are returned via a direct connection back to the originator.
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. While this article provides a basic class for searching, it's possible to provide your own. A better search class could be created that bypasses the built-in search mechanism in favor of enumerating the records directly (which is faster). This better search class could provide a richer SQL-like expression engine that supports IN
, NOT
, CASE
, BETWEEN
, LIKE
, type conversions and functions (NOW
, LEN
, SUBSTRING
, ISDATE
, etc.).
The next article focuses on a helper class that allows you to express a search criteria in a more SQL like manner, for example:
peercreatorid LIKE 'James *' OR (peerlastmodificationtime >
CAST('2003-01-31' as date) AND peerlastmodificationtime <
CAST('2003-02-28' as date))
instead of:
<or>
<clause attrib="peercreatorid"
type="string"
compare="equal">James *</clause>
<and>
<clause attrib="peerlastmodificationtime"
type="date"
compare="greater">2003-01-31</clause>
<clause attrib="peerlastmodificationtime"
type="date"
compare="less">2003-02-28</clause>
</and>
</or>
The helper class provides methods to convert from the SQL-like search expression to the required XML search criteria format, and vise versa.
Stay tuned for more articles on the following topics:
- Peer Name Resolution - Windows Vista Enhancements
- Peer Graph - Search Criteria Helper
- Peer Graph - Importing and Exporting a Database
- Peer Groups and Identity
- Peer Collaboration - People Near Me
- Peer Collaboration - EndPoints
- Peer Collaboration - Capabilities
- Peer Collaboration - Presence
- Peer Collaboration - Invitations
If you have suggestions for other topics, please leave a comment.
History