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

对等图形 - 记录

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.24/5 (7投票s)

2005 年 12 月 29 日

8分钟阅读

viewsIcon

93893

downloadIcon

878

使用 Microsoft 的 Peer-to-Peer 技术在 Peer Graph 中发布共享数据

背景

Microsoft 的 Peer-to-Peer Graphing 技术为 Windows peer-to-peer 应用程序提供了稳定、可靠和健壮的通信基础设施。Peers 使用 Peer Name Resolution Protocol (PNRP - 一个无服务器 DNS) 在图内注册和发现其他 peers。图是连接 peer 网络内的 peers、服务和资源的基石。Peer 可以是用户交互式应用程序、服务或资源。Graphing 可以高效可靠地在 peers 之间传递数据。

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

引言

本文介绍了 peer 将共享数据发布到 peer-to-peer 图中的概念。一旦打开一个图,任何 peer 都可以发布数据记录。记录有过期时间。记录可以包含服务的存在、CPU 可用于执行更多工作的可用性、定期更新的资源状态,或者您最新博客文章的 URL。如果要发布的信息是静态的或相对较小,则可以将信息作为数据附加到记录中。否则,记录的数据通常应包含一个 URL 或 peer 地址,以访问经常更改或大量的信息。

发布到图中的任何记录都会自动与图中的所有 peers 共享,即使它们处于离线状态。当一个离线的 peer 重新上线时,它会首先与一个包含所有最新记录的 peer 同步。只要知道记录 ID,就可以更新现有记录的某些属性。还可以将记录标记为已删除。删除会逐渐在图中传播,但最终会在过期时被正确删除。

本文着重于在图表中添加、更新、删除和枚举记录的机制。本系列文章的其余两篇将讨论如何将属性(元数据)与记录关联以及如何通过匹配属性来搜索记录。

记录属性

PeerRecord 类是托管 API 使用和返回的 PEER_RECORD 数据结构的包装器。PeerRecord 包含以下属性

名称 描述 访问
ID

由非托管 API 生成的记录的 GUID ID。

只读
类型 应用程序特定的记录类型,例如,聊天消息或文件描述。 只读
版本 由非托管 API 生成和维护的记录的版本。 只读
CreatedBy 将记录发布到图的用户身份。 只读
ModifiedBy 最后一次更新发布到图中的记录的用户身份。 只读
属性 包含描述记录内容的元数据的 XML 字符串。 读/写
CreationTime 记录首次发布到图中的时间。 只读
ExpirationTime 记录将从图中删除的估计时间。 读/写
LastModifiedTime 记录最后一次更新并重新发布到图中的时间。 只读
DataAsString 附加到记录的任何数据,表示为字符串。 读/写
DataAsStream 附加到记录的任何数据,表示为流。 读/写

创建记录

可以通过三种方式创建 PeerRecord。第一种方法在将记录添加到图并发布之前使用。此方法涉及调用图的 CreatePeerRecord 方法。

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

此方法接受两个参数:记录的类型,以及记录将过期的自当前时间起的时间间隔。

第二种方法是调用图的 GetRecord 方法的结果来创建 PeerRecord

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

GetRecord 方法通常在收到指示特定记录状态更改的事件后调用。此方法采用始终作为事件通知一部分提供的记录 ID。GetRecord 方法调用底层的 PeerGraphGetRecord API。

创建 PeerRecord 的最后一种方法是在枚举图中的记录时产生的。

添加记录

在调用 CreatePeerRecord 创建记录后,使用适当的数据设置可写属性,然后调用图的 AddRecord 方法。

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

AddRecord 方法将数据打包并调用底层的 PeerGraphAddRecord API。如果记录成功添加,则返回记录的 ID。

更新记录

发布到图中的记录可以由连接到该图的任何应用程序或用户更新。只能修改四个可写属性:AttributesExpirationTimeDataAsStringDataAsStream。其他属性,如 ModifiedByLastModifiedTimeVersion,由非托管 API 自动更新。使用图的 UpdateRecord 方法将记录重新发布到图中。

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

当您只想更改发布的数据或属性时,请使用带单个参数的第一种方法。此方法将数据打包并调用非托管 PeerGraphUpdateRecord API。

当您还想刷新记录的过期时间时,请使用第二种方法。

删除记录

可以在记录过期之前从图中删除记录。例如,发布到图中的文件在本地被删除。使用图的 DeleteRecord 方法删除已发布到图中的现有记录。

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

此方法调用底层的 PeerGraphDeleteRecord API。

记录更改事件

当图中记录的状态发生更改时,PeerGraph 类会处理 PEER_GRAPH_EVENT_RECORD_CHANGED 通知。

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

内部的 HandleEventRecordChanged 方法打包记录 ID 和类型,并触发 RecordChanged 事件。收到记录通知有四种原因:记录已添加、记录已更新、记录已删除或记录已过期。应用程序只能在收到添加或更新通知时调用图的 GetRecord 方法。记录过期或被删除后,无法获得记录信息。

枚举记录

PeerGraph 类提供了几种枚举发布到图中的记录的方法。每种方法提供不同的过滤返回记录的方式。

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

Records 属性返回发布到图中的所有记录(无过滤)。第一个 GetRecords 方法允许按类型和身份进行过滤。第二种方法仅允许按类型进行过滤。最后一种方法仅允许按身份进行过滤。在所有情况下,都会返回支持标准 IEnumerable 接口的 PeerRecordCollection 类。此类使用非托管 PeerGraphEnumRecords API 来返回和枚举匹配的记录。

使用示例应用程序

示例应用程序允许您首先创建一个具有初始身份的图(非安全 peer 名称 0.ChatRecords)。第一个实例应以该身份打开。它会暂停几秒钟以查找其他实例,然后开始监听。每个后续的应用程序实例都应使用不同的身份打开图。这些实例将连接到最近的 peer 并进行同步。每个应用程序实例都是一个 peer。

顶部列表显示图中所有已发布记录的 ID。选择一个项目可显示其属性。双击记录可将其删除。

中间列表显示所有操作和传入事件的诊断日志。双击以清除列表。

底部文本框允许您输入消息。单击“发送”按钮将消息作为记录发布到图中。

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

如果未选择现有记录,则输入的文本将与新记录关联。调用 CreatePeerRecord 来创建记录,将文本作为数据添加,然后将记录添加到图中。否则,当选择现有记录时,将检索该记录,使用输入的文本更新数据,然后更新图。

当图中的记录状态发生变化时,应用程序的 OnRecordChanged 事件会被触发。当记录添加时,它会将记录的 ID 添加到顶部列表。当记录被删除或过期时,它会从顶部列表中删除记录的 ID。在所有情况下,都会记录一条诊断消息。

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

关注点

为了让应用程序缓存 PeerRecord 类,它必须复制非托管数据。原因是在 PEER_RECORD 打包后,其内存将被释放。为了简化内存释放,Microsoft 选择创建一个连续的内存块来存储记录及其数据。也就是说,数据在同一内存块中跟随记录。结果是,一旦内存被释放,数据就无法再从托管代码中访问(IntPtr 无效)。为了保持数据处于打包状态,非托管数据会被复制到非托管内存中。可惜的是,Microsoft 没有提供更好的方法来解决这个问题。

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

我需要进行一些基准测试,以确定处理此问题的更优方法,尤其是当大部分时间仅用于读取打包时。

资源链接

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

结论

希望本文对您有所帮助。下一篇文章将重点介绍如何将 XML 元数据与记录关联。敬请关注以下主题的更多文章

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

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

历史

  • 初始修订版

许可证

本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。

作者可能使用的许可证列表可以在此处找到。

© . All rights reserved.