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

对等组 - 创建、打开和删除

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.09/5 (2投票s)

2006年2月9日

8分钟阅读

viewsIcon

40207

downloadIcon

465

使用 Microsoft 的点对点技术创建、打开和删除对等组。

背景

Microsoft 的点对点分组技术为 Windows 点对点应用程序的通信提供了稳定、可靠和健壮的基础设施。分组 API 基于图形 API,但增加了健壮的安全系统。对等方使用对等名称解析协议(PNRP - 无服务器 DNS)在组内注册和发现其他对等方。组提供了一种安全的机制,用于连接对等网络中的对等方、服务和资源。分组允许数据在对等方之间高效、可靠和安全地传递。

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

引言

本文介绍了 PeerGroup 托管类,它是所有点对点分组 API 的主要包装器。本文基于前一篇文章,该文章介绍了点对点组并详细描述了标识。

启动

在调用任何其他点对点分组 API 之前,必须调用 PeerGroupStartup 来初始化组。同样,在应用程序退出之前,必须调用 PeerGroupShutdown 来清理其在组中的存在。为了确保这一点,提供了一个静态单例类。PeerGroup 类包含一个引用,以确保在任何其他分组 API 使用之前创建单例。因此,您无需记住执行此操作。以下代码显示了单例类

sealed class PeerGroupService
{
  private PeerGroupService()
  {
    Peer.Graph.PEER_VERSION_DATA data;
    uint hr = PeerGroupNative.PeerGroupStartup(1, out data);
    if (hr != 0) throw new PeerGroupException(hr);
  }
  ~PeerGroupService()
  {
    PeerGroupNative.PeerGroupShutdown();
  }
  public static readonly PeerGroupService Instance = new PeerGroupService();
}

PeerGroup 类

PeerGroup 类的目的是公开所有对等分组功能。但是,本文开头的代码下载只实现了创建、打开和删除组的功能。它还为点对点组支持的一些事件通知提供了事件处理程序。未来的文章将扩展此类的功能,以公开整个点对点分组 API 集。

创建组

使用组的第一步是创建一个组。一个组只需要由第一个发布它的对等方创建一次。该组由创建它的标识拥有,并且是该组的初始管理员。

静态 Create 方法需要三个参数来创建组:标识、组名和友好名称。标识是通过先前调用 PeerIdentity Create 方法创建的。组名是组的简称,例如,“我的游戏”或“儿童图片”。友好名称提供有关组的附加信息,例如描述或组创建者的电子邮件联系方式。

以下代码展示了 Create 方法如何包装底层的 PeerGroupCreate API

public static void Create(string GroupName, 
                   PeerIdentity Creator, string FriendlyName)
{
  PeerGroupProperties properties = new PeerGroupProperties(null, 
                                   GroupName, Creator.Identity);
  properties.FriendlyName = FriendlyName;

  PEER_GROUP_PROPERTIES props = properties.Convert();
  IntPtr hGroup;
  uint hr = PeerGroupNative.PeerGroupCreate(ref props, out hGroup);
  if (hr != 0) throw new PeerGroupException(hr);

  hr = PeerGroupNative.PeerGroupClose(hGroup);
  if (hr != 0) throw new PeerGroupException(hr);
}

在幕后,会在“Documents and Settings\<User Name>\Application Data\PeerNetworking”文件夹中创建一个图形数据库。一个与标识匹配的子文件夹(例如,47e322b5473094981ad20777bed3fe71d009ec8a.User1)包含子文件夹(例如,7cb01116ac4b9fa469ef2bdf808a662a40fdb78e.Test Group),每个子文件夹中包含此标识创建的每个组的数据库。数据库名称始终为“grouping”。

访问组

只能通过枚举与标识关联的组来访问组。访问 PeerIdentityGroups 属性将返回一个 PeerGroupCollection。枚举此集合以发现当前与标识关联的所有 PeerGroup 实例。一旦选择了 PeerGroup 实例,就可以访问其方法和属性。

foreach (PeerGroup group in Identity.Groups)
{
  if (group.GroupName == "Test Group")
    group.Open();
  else
    group.Delete();
}

组属性

在内部,PeerGroupProperties 类维护有关组的当前信息。此类包装了封送的 PEER_GROUP_PROPERTIES 数据结构,并为结构中的每个字段公开属性,如以下代码所示

public class PeerGroupProperties
{
  private PeerGroup group;
  private PEER_GROUP_PROPERTIES properties;

  internal PeerGroupProperties(PeerGroup Group, 
           string GroupName, string Identity)
  {
    group = Group;
    properties = new PEER_GROUP_PROPERTIES();
    properties.dwSize = 
      Marshal.SizeOf(typeof(PEER_GROUP_PROPERTIES));
    properties.pwzClassifier = GroupName;
    properties.pwzCreatorPeerName = Identity;
    properties.ulMemberDataLifetime = 2419200;
    properties.ulPresenceLifetime = 300;
  }

  public PeerGroupPropertyAction Action
  {
    get { return properties.dwFlags; }
    set { properties.dwFlags = value; }
  }

  public string Cloud
  {
    get { return properties.pwzCloud; }
  }

  public string GroupName
  {
    get { return properties.pwzClassifier; }
  }

  internal string InternalGroupName
  {
    get { return properties.pwzGroupPeerName; }
    set { properties.pwzGroupPeerName = value; }
  }

  public string Creator
  {
    get { return properties.pwzCreatorPeerName; }
  }

  public string FriendlyName
  {
    get { return properties.pwzFriendlyName; }
    set { properties.pwzFriendlyName = value; Update(); }
  }

  public string Comment
  {
    get { return properties.pwzComment; }
    set { properties.pwzComment = value; Update(); }
  }

  public int MemberDataLifetime
  {
    get { return properties.ulMemberDataLifetime; }
  }

  public int PresenceLifetime
  {
    get { return properties.ulPresenceLifetime; }
  }

  internal void Update()
  {
    if (group != null && group.hGroup != IntPtr.Zero)
    {
      uint hr = PeerGroupNative.PeerGroupSetProperties(group.hGroup, ref properties);
      if (hr != 0) throw new PeerGroupException(hr);
    }
  }
}

在大多数情况下,默认值就是您所需要的一切。任何可写入的属性都会导致调用非托管 PeerGroupSetProperties API,以更新组中的底层属性。

通过访问 Properties 属性,这些属性也可以在打开组后检索。此属性使用非托管 PeerGroupGetProperties API 获取当前组属性,如以下代码所示

public PeerGroupProperties Properties
{
  get
  {
    if (hGroup != IntPtr.Zero)
    {
      IntPtr ppGroupProperties;
      uint hr = PeerGroupNative.PeerGroupGetProperties(hGroup, 
                                       out ppGroupProperties);
      if (hr != 0) throw new PeerGroupException(hr);

      properties = new PeerGroupProperties(this, 
                      (PEER_GROUP_PROPERTIES)Marshal.PtrToStructure(
                      ppGroupProperties,typeof(PEER_GROUP_PROPERTIES)));
    }
    return properties;
  }
}

删除组

PeerGroup 类的 Delete 方法允许您删除由所有者标识创建的点对点组。将调用底层的 PeerGroupDelete API,如以下代码所示

public void Delete()
{
  if (hGroup != IntPtr.Zero) Close();

  uint hr = PeerGroupNative.PeerGroupDelete(
            owner.Identity, properties.InternalGroupName);
  if (hr != 0)
      throw new PeerGroupException(hr);
}

打开组

PeerGroup 类的 Open 方法打开并连接到组。首先,调用底层的 PeerGroupOpen API 来打开组。此方法传递所有者的标识、内部组名和 NULL(表示 Global_ 云)。接下来,进行调用以注册事件通知。最后,调用底层的 PeerGroupConnect API 方法,该方法在 PNRP 中搜索组中的其他对等方,并向 PNRP 注册安全对等名。

public void Open()
{
  if (hGroup != IntPtr.Zero) Close();

  uint hr = PeerGroupNative.PeerGroupOpen(owner.Identity, 
            properties.InternalGroupName, 
            IntPtr.Zero, out hGroup);
  if (hr != 0)
      throw new PeerGroupException(hr);

  RegisterForEvents();
  hr = PeerGroupNative.PeerGroupConnect(hGroup);
  if (hr != 0) throw new PeerGroupException(hr);
}

事件

在连接之前,包装器会注册一个事件通知回调,允许应用程序在组中发生更改时接收事件。以下代码显示了内部 RegisterForEvents 方法如何使用 PeerGroupRegisterEvent 方法来注册事件

private void RegisterForEvents()
{
  if (hPeerEvent != IntPtr.Zero) return; // already registered

  AutoResetEvent sendEvent = new AutoResetEvent(false);
  Handle = ThreadPool.RegisterWaitForSingleObject(sendEvent, 
           new WaitOrTimerCallback(PeerEventWorker), null, -1, false);

  PEER_GROUP_EVENT_REGISTRATION[] info = 
  {
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_STATUS_CHANGED),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_PROPERTY_CHANGED),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_RECORD_CHANGED),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_DIRECT_CONNECTION),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_NEIGHBOR_CONNECTION),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_INCOMING_DATA),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_MEMBER_CHANGED),
    new PEER_GROUP_EVENT_REGISTRATION(
        PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_CONNECTION_FAILED)
  };

  int size = Marshal.SizeOf(info[0]);
  IntPtr infoptr = Marshal.AllocCoTaskMem(info.Length*size);

  int offset = 0;
  foreach (PEER_GROUP_EVENT_REGISTRATION item in info)
  {
    Marshal.StructureToPtr(item, 
            (IntPtr)(infoptr.ToInt32()+offset), false);
    offset += size;
  }
  uint result = PeerGroupNative.PeerGroupRegisterEvent(hGroup, 
                sendEvent.Handle, info.Length, infoptr, out hPeerEvent);
  if (result != 0)
      Marshal.ThrowExceptionForHR((int)result);
}

PEER_GROUP_EVENT_REGISTRATION 数据结构包含一个指示要监视的事件类型的字段。这些数据结构的数组可以传递给注册方法。上面大部分代码负责将此数据结构封送成一块非托管内存。

WaitOrTimerCallback 表示在组中发生更改时执行的回调方法。RegisterWaitForSingleObject 使用无限超时将此委托注册到默认线程池。最后,事件句柄和数据结构传递给 PeerGroupRegisterEvent

以下代码显示了工作线程如何处理传入事件

private void PeerEventWorker(object xdata, bool timedOut)
{
  while (true)
  {
    IntPtr evptr;
    uint result = PeerGroupNative.PeerGroupGetEventData(hPeerEvent, 
                                                        out evptr);
    if (result == Peer.Graph.PeerGraphNative.PEER_S_NO_EVENT_DATA 
                                          || evptr == IntPtr.Zero)
        break;
    if (result != 0) Marshal.ThrowExceptionForHR((int)result);

    PEER_GROUP_EVENT_DATA data = (PEER_GROUP_EVENT_DATA)
                                  Marshal.PtrToStructure(evptr, 
                                  typeof(PEER_GROUP_EVENT_DATA));
    IntPtr dataptr = (IntPtr)(evptr.ToInt32() + 
                      Marshal.SizeOf(typeof(PEER_GROUP_EVENT_DATA)));

    switch (data.eventType)
    {
      case PEER_GROUP_EVENT_TYPE.PEER_GROUP_EVENT_STATUS_CHANGED:
      HandleEventStatusChanged(dataptr);
      break;
    //...
    }
    PeerGroupNative.PeerFreeData(evptr);
  }
}

调用 PeerGroupGetEventData 方法获取每个事件。循环确保所有事件都继续进行,直到返回 PEER_S_NO_EVENT_DATA,表示没有更多事件。每个事件数据结构都被封送,并使用 switch 语句处理每个事件类型。

事件类型

以下列表总结了 PeerGroup 类公开的事件类型,并描述了每个事件何时触发

StatusChanged  -  表示对等方在组中的状态(监听,有连接)
PropertyChanged  -  组的一个或多个属性已更改
RecordChanged  -  发布到图表的数据已更改
ConnectionChanged  -  与远程对等方或直接连接的连接已更改(已连接、已断开连接、失败)
IncomingData  -  已从与另一个对等方的直接连接接收到私有数据
MemberChanged  -  组成员在组中的状态已更改(已连接、已断开连接、已加入、已更新)

当前示例应用程序仅实现了 StatusChanged 事件。未来的文章将演示其他事件的典型用法。

使用示例应用程序

示例应用程序允许您选择现有标识并创建、打开、关闭和删除点对点组。

上面显示的“标识”选项卡允许您查看现有标识。要创建标识,请输入名称并单击“创建”按钮。要删除标识,请在列表中选择标识并单击“删除”按钮。

“创建”选项卡允许您创建一个与所选标识关联的组。输入组名和友好名称(可以为空),然后单击“创建”按钮创建组。

切换到“组”选项卡以查看标识所属的组列表。选择一个组以查看其属性。单击“打开”按钮以打开并连接到组。打开后,对“注释”或“友好名称”属性的任何更改都会立即更新。单击“关闭”按钮以关闭所选组。单击“删除”按钮以删除组。请注意,当在列表中选择不同的组时,演示不跟踪哪些组已打开和关闭。

看点

netsh

使用 netsh 命令可以发现另一种查看打开组时幕后发生的情况的方法。使用 netsh,切换到“p2p pnrp cloud”上下文。您会注意到打开组后,PNRP 条目会添加到 Global_ 云中。使用“show names”查看注册的名称。以下是您应该看到的输出示例

P2P Name: 8264f208a7cddc30933f3e0b0c7ea2e143e6953e.participant
Identity: 47e322b5473094981ad20777bed3fe71d009ec8a.User1
Comment:
PNRP ID:  7e154375ac617a5e2c1d86cb5ab45793.3ffe831f400419520f2185acc317053e
State:    OK
IP Addresses: [3ffe:831f:4004:1952:8000:5739:bb6f:38ea]:3587 tcp

切换到“p2p idmgr”上下文并使用“show groups ALL”命令查看标识的所有配置组。以下是您应该看到的输出示例

Identity P2PID = d3d9e1b0ec8accfe3fc55a0e68ef2f6a
      Peername = 47e322b5473094981ad20777bed3fe71d009ec8a.User1

   Group P2PID = 3d0e6725c882cea35327d7b533ebaf45 GMC chain len=2
      Peername = 8264f208a7cddc30933f3e0b0c7ea2e143e6953e.Test Group
      Friendly = Hi there!
  Valid: Start = 2/4/2006 13:15:32 End =2/4/3006 14:15:32 (Valid)

切换到“p2p group”上下文并使用“show address <Group P2PID>”命令查看当前绑定到组的地址。在上述情况下,“show address 3d0e6725c882cea35327d7b533ebaf45”会产生以下输出

Resolving participant node...
Found a participant node.
Participant node is listening on following addresses:
    [3ffe:831f:4004:1952:8000:5739:bb6f:38ea]:3587

可扩展性

使用点对点组共享文件或提供线程讨论的真实应用程序必须从 PeerGroup 类派生自定义类。默认情况下,PeerIdentity 类的 Groups 属性返回一个 PeerGroupCollection 类,该类使用 PeerGroupFactory 类来创建 PeerGroup 类。但是,设置 PeerIdentity 类的 Factory 属性允许您提供自己的工厂来创建自己的派生自定义类。

identity.Factory = new MyPeerGroupFactory();

public class MyPeerGroup : PeerGroup
{
}

public class MyPeerGroupFactory : PeerGroupFactory
{
  public override PeerGroup CreatePeerGroup(PeerIdentity Owner)
  {
    return new MyPeerGroup(Owner);
  }
}

未来的文章将展示可扩展性点,允许连接、对等体、记录和搜索的自定义类,类似于为 PeerGraph 类提供的类。

故障排除

在创建标识或组时,您可能会遇到“无密钥访问”错误。以下知识库文章有助于解释其含义以及如何解决问题:点对点框架 API 返回“PEER_E_NO_KEY_ACCESS”错误消息

资源链接

我发现以下资源对于理解对等组非常有用

结论

我将在未来的文章中介绍更多的分组 API。请继续关注下一篇文章,该文章将描述如何创建邀请以邀请其他身份加入组,以及对等方如何使用这些邀请加入组。

  1. 对等组 - 邀请和加入
  1. 对等协作 - 附近的人
  2. 对等协作 - 端点
  3. 对等协作 - 能力
  4. 对等协作 - 在线状态
  5. 对等协作 - 邀请
  1. 对等名称解析 - Windows Vista 增强功能

如果您对其他主题有建议,请留言。最后,如果您已经读到这里,别忘了投票!

历史

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