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






4.09/5 (2投票s)
2006年2月9日
8分钟阅读

40207

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”。
访问组
只能通过枚举与标识关联的组来访问组。访问 PeerIdentity
的 Groups
属性将返回一个 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。请继续关注下一篇文章,该文章将描述如何创建邀请以邀请其他身份加入组,以及对等方如何使用这些邀请加入组。
- 对等组 - 邀请和加入
- 对等协作 - 附近的人
- 对等协作 - 端点
- 对等协作 - 能力
- 对等协作 - 在线状态
- 对等协作 - 邀请
- 对等名称解析 - Windows Vista 增强功能
如果您对其他主题有建议,请留言。最后,如果您已经读到这里,别忘了投票!
历史
- 初始版本。