65.9K
CodeProject 正在变化。 阅读更多。
Home

在 Microsoft 点对点技术中的对等图中使用关联的属性元数据

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.17/5 (2投票s)

2006年1月6日

7分钟阅读

viewsIcon

26463

downloadIcon

177

在 Microsoft 的对等网络技术中使用对等图关联属性元数据。

背景

Microsoft 的对等网络图技术为 Windows 对等网络应用程序的通信提供了稳定、可靠且健壮的基础设施。对等节点使用对等名称解析协议 (PNRP - 无服务器 DNS) 在图内注册和其他对等节点。图是连接对等节点、服务和资源的基础。对等节点可以是用户交互式应用程序、服务或资源。图技术可以高效可靠地在对等节点之间传递数据。

Microsoft 的整个点对点技术通过最新的平台 SDK 以 C/C++ API 调用形式公开。但是,本文中的代码展示了如何使用 C# 从 .NET 托管代码调用这些 API。

引言

本文介绍了在对等网络图的对等节点记录中关联元数据的概念。正如我们在上一篇文章中所学到的,一条记录可以表示服务的存在、CPU 完成更多工作的可用性、定期更新的资源状态,或者您最新博客文章的 URL。为了简化搜索,而不是将这些信息隐藏在记录数据中,可以将元数据附加到每条记录。用 Microsoft 的术语来说,这种元数据称为属性。属性以 XML 的形式表示,以实现更广泛的功能。

Microsoft 提供了一个简单的架构,属性 XML 片段必须符合该架构。低级别的 PeerGraph API 将此 XML 片段视为一个简单的字符串。本文重点介绍用于管理属性的 `PeerAttributes` 辅助类,以及将信息转换为所需 XML 格式的方法。它还描述了在实现属性支持后对 `PeerRecord` 类的更新。

XML架构

XML 架构非常简单。根元素必须命名为 `<attributes>`。它包含必须命名为 `<attribute>` 的子元素。每个 `<attribute>` 标签有两个 XML 属性:`name` 和 `type`。`name` 表示属性的名称,`type` 表示其类型。仅支持三种数据类型:`int`、`date` 和 `string`。值在开始和结束标签之间指定。以下是一个简单的示例:

<attributes>
  <attribute name="Type" type="string">
      4d5b2f11-6522-433b-84ef-a298e60757b0
  </attribute>
  <attribute name="CreationTime" type="date">
      2005-12-31T17:00:00.0000000-07:00
  </attribute>
</attributes>

示例代码中包含的 `Attributes.xsd` 文件包含架构。它从描述 记录属性架构的 MSDN 库网页复制而来。以下描述的 `Validate` 方法使用此架构。

PeerAttribute 类

`PeerAttribute` 类是一个简单的包装器,用于存储属性。该类包含三个属性:`name`(只读)、`type` 和 `value`(两者都可更新)。

public enum PeerAttributeType
{
  Int,
  String,
  Date
}

public class PeerAttribute
{
  private string name;
  private PeerAttributeType type;
  private object data;

  public PeerAttribute(string Name, PeerAttributeType Type, object Value)
  {
    name = Name;
    type = Type;
    data = Value;
  }

  public string Name
  {
    get { return name; }
  }

  public PeerAttributeType Type
  {
    get { return type; }
    set { type = value; }
  }

  public object Value
  {
    get { return data; }
    set { data = value; }
  }
}

PeerAttributes 集合

`PeerAttribues` 类继承自 `CollectionBase`,为 `PeerAttribute` 对象提供方便的存储和标准集合方法。除了下面描述的方法外,它还包括一个索引器(`Item[int] 和 `Item[string]),并重写了 `ToString` 方法以返回集合中存储的属性的 XML 等效项。

简单的添加方法

提供了四种方法,一次向集合中添加一个属性。

private void Add(string Name, PeerAttributeType Type, object Value)
{
  this.InnerList.Add(new PeerAttribute(Name, Type, Value));
}

public void Add(string Name, int Value)
{
  Add(Name, PeerAttributeType.Int, Value);
}

public void Add(string Name, DateTime Value)
{
  Add(Name, PeerAttributeType.Date, Value);
}

public void Add(string Name, string Value)
{
  Add(Name, PeerAttributeType.String, Value);
}

第一种方法使用给定的名称、类型和值创建一个 `PeerAttribute` 对象。其他三种方法是类型特定的版本,它们调用第一种方法。

添加对象方法

典型的对等网络应用程序将使用其自己的运行时对象来表示与共享节点或记录关联的元数据。剩余的 `Add` 方法允许开发人员传递此运行时对象,并使用反射从其属性中提取当前值作为属性。示例应用程序从 `System.IO.FileInfo` 对象中提取属性。

public void Add(object Instance)
{
  foreach (PropertyInfo prop in Instance.GetType().GetProperties())
  {
    try
    {
      object oValue = prop.GetValue(Instance, null);

      if (prop.PropertyType.FullName == "System.Int32")
        Add(prop.Name, (int)oValue);
      else if (prop.PropertyType.FullName == "System.DateTime")
        Add(prop.Name, (DateTime)oValue);
      else
        Add(prop.Name, oValue.ToString());
    }
    catch (Exception ex) 
    {
      // ignore exceptions
    }
  }
}

对于运行时对象公开的每个属性,都会提取其当前值和类型以创建 `PeerAttribute` 对象。

删除方法

在某些情况下,Add Object 方法可能会提取开发人员不希望公开为元数据的属性。`Remove` 方法允许按名称删除不需要的属性。

public void Remove(string Name)
{
  int i = 0;
  foreach (PeerAttribute attr in this.List)
  {
    if (attr.Name == Name)
    {
      this.List.RemoveAt(i);
      break;
    }
    i++;
  }
}

XML 属性

`XML` 属性的 `get 部分使用 `XmlTextWriter` 类将存储在集合中的属性转换为 XML 片段。

get
{
  if (this.List.Count == 0) return null;

  XmlTextWriter xt = new XmlTextWriter(new MemoryStream(), 
                     System.Text.UTF8Encoding.Unicode);
  xt.Formatting = Formatting.Indented;
  xt.WriteStartElement("attributes");
  foreach (PeerAttribute attr in this.List)
  {
    xt.WriteStartElement("attribute");
    xt.WriteAttributeString("name", attr.Name);
    xt.WriteAttributeString("type", StringFromType(attr.Type));
    string s = attr.Value.ToString();
    if (attr.Type == PeerAttributeType.Date)
      s = XmlConvert.ToString(Convert.ToDateTime(attr.Value));
    xt.WriteString(s);
    xt.WriteEndElement();
  }
  xt.WriteEndElement();
  xt.Flush();
  xt.BaseStream.Position = 0;
  return new StreamReader(xt.BaseStream).ReadToEnd();
}

`XML` 属性的 `set 部分将 XML 片段的每个 `<attribute> 元素转换为 `PeerAttribute` 对象并将其存储在集合中。`XmlTextReader` 用于识别每个要转换的元素。

set
{
  if (value == null || value == string.Empty) return;
  this.Clear();

  XmlTextReader xr = new XmlTextReader(new StringReader(value));

  string Name = string.Empty;
  PeerAttributeType Type = PeerAttributeType.String;
  object Value = null;

  while (xr.Read())
  {
    if (xr.NodeType == XmlNodeType.Element && 
        xr.Name == "attribute")
    {
      xr.MoveToFirstAttribute();
      int count = xr.AttributeCount;
      for (int i = 0; i < count; i++)
      {
        xr.MoveToAttribute(i);
        if (xr.Name == "name")
        {
          xr.ReadAttributeValue();
          Name = xr.Value;
        }
        else if (xr.Name == "type")
        {
          xr.ReadAttributeValue();
          Type = TypeFromString(xr.Value);
        }
      }
    }
    else if (xr.NodeType == XmlNodeType.Text)
    {
      Value = xr.Value;
    }
    else if (xr.NodeType == XmlNodeType.EndElement && 
             xr.Name == "attribute")
    {
      switch (Type)
      {
      case PeerAttributeType.Int:
        Add(Name, Convert.ToInt32(Value));
        break;
      case PeerAttributeType.Date:
        Add(Name, Convert.ToDateTime(Value));
        break;
      default:
        Add(Name, Value.ToString());
        break;
      }
    }
  }
}

第一个 `if-块处理提取 `name` 和 `type` 属性。中间的 `if`-块处理将值提取为文本。最后一个 `if`-块使用从前两个 `if`-块收集的信息来调用适当的 `Add` 方法并执行正确的日期类型转换。

Validate 方法

示例文件 `Attributes.xsd` 包含用于验证 XML 属性片段的架构,并作为嵌入式资源存储在示例应用程序中。该架构被加载并编译为 `PeerAttributes` 集合的静态(共享)字段。`Validate` 方法使用该架构来验证与集合中当前存储的属性等效的 XML 片段。

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

`Validate` 方法返回 `true 如果 XML 片段与架构匹配,否则返回 `false 如果引发了异常。永远不需要验证 XML 片段,但为了完整起见,包含了此功能。

对 PeerRecord 的更新

先前文章中的源代码将 `PeerRecord` 类的属性公开为 `string 属性。在本文中,已更新源代码以公开一个只读属性,该属性返回一个 `PeerAttributes` 集合。

private PeerAttributes attributes;
public PeerAttributes Attributes
{
  get { return attributes; }
}

在记录为与底层 API 方法一起使用而封送时,属性现在会自动转换为 XML 字符串片段或从 XML 字符串片段转换。

使用示例应用程序

示例应用程序允许您首先创建一个图(未加密的对等节点名称 ` 0.SharedFiles`)以及一个初始标识。第一个实例应使用此标识打开。它将暂停几秒钟以查找其他实例,然后开始侦听。应用程序的每个后续实例都应使用不同的标识打开图。这些实例将连接到最近的对等节点并进行同步。应用程序的每个实例都是一个对等节点。

使用“添加文件夹”按钮选择一个包含文件的文件夹。

private void Add_Click(object sender, System.EventArgs e)
{
  DialogResult result = folderBrowserDialog1.ShowDialog();
  if (result == DialogResult.OK)
  {
    string path = folderBrowserDialog1.SelectedPath;
    DirectoryInfo dinfo = new DirectoryInfo(path);
    // publish each file as a record
    foreach (FileInfo info in dinfo.GetFiles())
    {
      // note the record will expire after 1 minute
      PeerRecord record = graph.CreatePeerRecord(FILE_RECORD_TYPE, 
                                             new TimeSpan(0,1,0));
      record.Attributes.Add(info);
      record.DataAsString = info.Name;
      Guid recordId = graph.AddRecord(record);
    }
  }
}

选择文件夹后,文件夹中的每个文件都将作为一条记录发布到图。请注意,记录设置为在 1 分钟后过期。记录的属性设置为 `FileInfo` 类的属性。记录的数据设置为文件名。

private void OnRecordChanged(object sender, PeerGraphRecordChangedEventArgs e)
{
  PeerRecord record;
  FileItem item;
  switch (e.Action)
  {
  case PeerRecordAction.Added:
    record = graph.GetRecord(e.RecordId);
    LogMessage(@"Added", record.DataAsString);
    item = new FileItem(record.DataAsString, e.RecordId);
    listBox2.Items.Add(item);
    break;
...

添加记录后,`RecordChanged` 事件将触发,操作设置为 `Added`。使用已添加的 `RecordID` 对应的 `PeerRecord` 和包含文件名的记录数据创建简单的 `FileItem` 对象,该对象将文件名与列表中的记录 ID 相关联。

public class FileItem
{
  private string name;
  private Guid recordid;
  public FileItem(string Name, Guid RecordId)
  {
    name = Name;
    recordid = RecordId;
  }

  public string Name
  {
    get { return name; }
  }

  public Guid RecordId
  {
    get { return recordid; }
  }
}

单击列表框中的文件名,会在右侧文本框中显示与该记录关联的 XML 属性。`PeerAttributes` XML 属性用于将属性读取为 XML 片段。

private void listBox2_SelectedIndexChanged(object sender, System.EventArgs e)
{
  if (listBox2.SelectedIndex == -1)
    textBox3.Text = string.Empty;
  else
  {
    FileItem item = (FileItem)listBox2.SelectedItem;
    PeerRecord record = graph.GetRecord(item.RecordId);
    textBox3.Text = record.Attributes.Xml;
  }
}

下面的列表显示了所有操作和传入事件的诊断日志。双击清除列表。

关注点

在实现辅助类时,我遇到了一个关于属性处理日期的错误/限制。

<attributes>
  <attribute name="CreationTime" 
        type="date">1600-12-31T17:00:00.0000000-07:00</attribute>
</attributes>

似乎,早于 1754 年的年份会导致 `PeerGraphAddRecord``PeerGraphUpdateRecord` 方法抛出 0x80040E21 异常。经过一些调查,我发现了一个 Microsoft 知识库文章,解释了 Access ODBC 驱动程序无法插入早于 1753 年的日期。看起来对等网络图使用的底层存储机制基于 Jet 引擎。

资源链接

我发现以下资源对于理解对等图形非常有帮助

结论

希望您觉得这篇文章很有用。下一篇文章将重点介绍搜索匹配元数据信息的记录。请继续关注有关以下主题的更多文章:

  1. 对等名称解析 - Windows Vista 增强功能
  1. 对等图形 - 搜索
  2. Peer Graph - 导入和导出数据库
  1. 对等组和身份
  1. 对等协作 - 附近的人
  2. 对等协作 - 端点
  3. 对等协作 - 能力
  4. 对等协作 - 在线状态
  5. 对等协作 - 邀请

如果您对其他主题有建议,请留言。

历史

  • 初始版本。
© . All rights reserved.