对等协作 - 附近的人






4.22/5 (7投票s)
2006 年 4 月 18 日
9分钟阅读

58233

680
Microsoft Windows Vista 中的点对点协作技术简介。
背景
本文介绍了 Microsoft Windows Vista 中新的点对点协作技术。协作技术支持新一代无服务器应用程序,并提供
- 对等点的自动发现,
- 对等点存在和可用性的自动更新,
- 联系人和邀请的管理,以及
- 应用程序功能的发现和匹配。
命名和发现
点对点名称解析协议 (PNRP) 是当今 Windows 版本中存在的无服务器 DNS。它被点对点应用程序用于注册其存在并在本地网络或 Internet 上发现其他对等点。PNRP 功能非常强大,但是它严重依赖于正常的 IPv6 基础设施以及 IPv4 到 IPv6 的隧道。Windows Vista 在这方面包含了新的改进,但这将是另一篇文章的主题。
为了简化子网上的发现,Microsoft 在 Windows Vista 中添加了“我附近的人”功能。应用程序必须登录以提醒其他人其存在和功能。登录后,应用程序可以发布数据以指示本地计算机上已安装或正在运行哪些应用程序,或者启动应用程序需要哪些数据。应用程序还可以使用底层协作 API 枚举子网上的其他对等点并发现其应用程序功能。“我附近的人”为用户提供了一种简单的方式,可以发现同一办公室或会议室中的人员,以创建临时、实时工作组来共享资源和数据。此功能是否会在未来的 Windows XP 服务包中提供尚待观察。
联系人
一旦您发现了一个想要长期协作的人,您就可以将其保存为联系人。协作 API 包含管理在子网中发现的联系人并将它们存储在 Windows 地址簿 (WAB) 中的方法。它还提供了使用通用注册应用程序创建协作邀请的 API。最后,提供了 API 来监视联系人的存在,类似于在即时通讯应用程序中监视联系人状态的方式。
应用程序功能
应用程序可以在安装期间使用协作 API 来注册其功能和/或启动应用程序所需的参数。安装或运行相同应用程序的其他用户可以发现并使用此信息进行协作。
这项新功能与现有的点对点图形和分组技术相结合,将使开发人员能够创建新的创新应用程序。
引言
Microsoft 的整个点对点技术通过最新的平台 SDK 以 C/C++ API 调用的形式公开。然而,本文中的代码展示了如何使用 C# 从 .NET 托管代码调用这些 API。示例应用程序包含一个 `PeerCollab` 类,它实现了 `SignIn` 和 `SignOut` 方法来控制本地子网上“我附近的人”的存在。此外,还提供了委托来显示同一子网上其他“我附近的人”存在状态的变化。虽然 PeerCollab 类隐藏了使用 Microsoft 点对点协作 API 的细节(为了简化编程模型),但下面概述了非托管调用的流程。
启动
在调用任何其他点对点协作 API 之前,必须调用 PeerCollabStartup
以初始化服务。同样,在应用程序退出之前,必须调用 PeerCollabShutdown
。为了确保这一点,提供了一个静态单例类。PeerCollab
类包含一个引用,以保证在任何其他协作 API 使用之前创建单例。因此,您不必记住这样做。以下代码显示了单例类
sealed class PeerCollabService
{
private PeerCollabService()
{
uint hr = PeerCollabNative.PeerCollabStartup(1);
if (hr != 0) throw new PeerCollabException(hr);
}
~PeerCollabService()
{
PeerCollabNative.PeerCollabShutdown();
}
public static readonly PeerCollabService
Instance = new PeerCollabService();
}
PeerCollab 类
PeerCollab
类的目的是公开所有点对点协作功能。然而,本文开头的代码下载只实现了登录、注销、显示“我附近的人”以及处理事件以监视本地子网上的“我附近的人”的到来和离开的能力。
PeerCollab
类构造函数无需任何参数。为了让您的应用程序发布其存在,它必须调用 SignIn
方法。
登录
SignIn
方法接受一个参数,该参数决定了如何监控您的存在
NearMe
允许同一子网上的其他应用程序发现并监控您的存在。Internet
允许其他联系人监控您的存在。All
允许子网和互联网用户监控您的存在。None
阻止其他人看到您的应用程序。
请注意,示例应用程序仅演示了 NearMe
选项。以下代码显示了 SignIn
方法如何调用底层 PeerCollabSignin
API
public void SignIn(PeerSignInOption Options)
{
uint hr =
PeerCollabNative.PeerCollabSignin(IntPtr.Zero,
Options);
if (hr != 0) throw new PeerCollabException(hr);
RegisterForEvents();
}
请注意,根据 Microsoft 的文档,第一个参数应该接受父窗口的 HWND
句柄。在撰写本文时,使用 2006 年 2 月的 Windows Vista CTP 版本,此参数无法从 .NET 中使用。此参数的目的是显示“确认隐私”窗口。在您确认隐私声明之前,当前 Windows 帐户无法登录到“我附近的人”,并且示例应用程序将记录 PEER_E_PRIVACY_DECLINED
异常。在“控制面板”->“网络和 Internet”中,单击“我附近的人”小程序。第一次执行此操作时,应该会弹出一个窗口要求您确认隐私声明。
一旦 Microsoft 支持 .NET 中的 HWND
参数,“确认隐私”窗口就应该出现在示例应用程序中,而不是立即抛出异常。
注销
SignOut
方法支持与 SignIn
方法相同的选项,并允许部分或完全更改状态。以下代码显示了 SignOut
方法如何调用底层 PeerCollabSignout
API。
public void SignOut(PeerSignInOption Options)
{
uint hr = PeerCollabNative.PeerCollabSignout(Options);
if (hr != 0) throw new PeerCollabException(hr);
UnregisterForEvents();
}
请注意,登录或注销会影响当前 Windows 帐户下运行的所有其他点对点协作应用程序。如果您计划从服务中使用协作,则应考虑为您的服务创建单独的独立帐户。
状态
随时使用 Status
属性来确定当前应用程序的登录状态。以下代码显示了 Status
属性如何调用底层 PeerCollabGetSigninOptions
API。
public PeerSignInOption Status
{
get
{
PeerSignInOption Options;
uint hr = PeerCollabNative.PeerCollabGetSigninOptions(out Options);
if (hr != 0) throw new PeerCollabException(hr);
return Options;
}
}
我附近的人集合
一旦您的应用程序登录,您就可以使用 PeopleNearMe
属性枚举同一子网上的“我附近的人”。
public PeopleNearMeCollection PeopleNearMe
{
get { return new PeopleNearMeCollection(); }
}
此属性返回一个 PeopleNearMeCollection
集合。该集合实现了 IEnumerable
接口。此接口的 Reset
方法调用底层的 PeerCollabEnumPeopleNearMe
API 以检索同一子网上“我附近的人”的当前列表。
public void IEnumerable.Reset()
{
uint hr;
if (hPeerEnum != IntPtr.Zero)
{
hr = PeerCollabNative.PeerEndEnumeration(hPeerEnum);
if (hr != 0) throw new PeerCollabException(hr);
hPeerEnum = IntPtr.Zero;
}
hr = PeerCollabNative.PeerCollabEnumPeopleNearMe(out hPeerEnum);
if (hr != 0) throw new PeerCollabException(hr);
uint count;
hr = PeerCollabNative.PeerGetItemCount(hPeerEnum, out count);
if (hr != 0) throw new PeerCollabException(hr);
this.count = (int)count;
index = -1;
}
事件
协作 API 允许应用程序在其他应用程序的存在或功能发生变化时注册并接收事件。以下代码显示了内部 RegisterForEvents
方法如何使用 PeerCollabRegisterEvent
方法注册事件。
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_COLLAB_EVENT_REGISTRATION[] info =
{
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_WATCHLIST_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_ENDPOINT_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_ENDPOINT_PRESENCE_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_ENDPOINT_APPLICATION_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_ENDPOINT_OBJECT_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_MY_ENDPOINT_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_MY_PRESENCE_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_MY_APPLICATION_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_MY_OBJECT_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_PEOPLE_NEAR_ME_CHANGED),
new PEER_COLLAB_EVENT_REGISTRATION(
PEER_COLLAB_EVENT_TYPE.PEER_EVENT_REQUEST_STATUS_CHANGED)
};
int size = Marshal.SizeOf(info[0]);
IntPtr infoptr = Marshal.AllocCoTaskMem(info.Length*size);
int offset = 0;
foreach (PEER_COLLAB_EVENT_REGISTRATION item in info)
{
Marshal.StructureToPtr(item, (IntPtr)(infoptr.ToInt32()+offset), false);
offset += size;
}
uint result = PeerCollabNative.PeerCollabRegisterEvent(sendEvent.Handle,
info.Length, infoptr, out hPeerEvent);
if (result != 0) Marshal.ThrowExceptionForHR((int)result);
}
PEER_COLLAB_EVENT_REGISTRATION
数据结构包含一个指示要监视的事件类型的字段。这些数据结构的数组可以传递给注册方法。上面的大部分代码负责将此数据结构封送到非托管内存块中。
WaitOrTimerCallback
表示一个回调方法,当 Collab 中发生变化时执行该方法。RegisterWaitForSingleObject
使用无限超时将此委托注册到默认线程池。最后,事件句柄和数据结构被传递给 PeerCollabRegisterEvent
。
以下代码显示了工作线程如何处理传入事件。
private void PeerEventWorker(object xdata, bool timedOut)
{
while (true)
{
IntPtr evptr;
uint result =
PeerCollabNative.PeerCollabGetEventData(hPeerEvent, out evptr);
if (result == PeerCollabNative.PEER_S_NO_EVENT_DATA ||
evptr == IntPtr.Zero)
break;
if (result != 0) Marshal.ThrowExceptionForHR((int)result);
PEER_COLLAB_EVENT_DATA data =
(PEER_COLLAB_EVENT_DATA)Marshal.PtrToStructure(evptr,
typeof(PEER_COLLAB_EVENT_DATA));
IntPtr dataptr = (IntPtr)(evptr.ToInt32() +
Marshal.SizeOf(typeof(PEER_COLLAB_EVENT_DATA)));
switch (data.eventType)
{
case PEER_COLLAB_EVENT_TYPE.PEER_EVENT_REQUEST_STATUS_CHANGED:
HandleEventRequestStatusChanged(dataptr);
break;
//...
}
PeerCollabNative.PeerFreeData(evptr);
}
}
调用 PeerCollabGetEventData
方法以获取每个事件。循环确保所有事件都被处理,直到返回 PEER_S_NO_EVENT_DATA
表示没有更多事件。每个事件数据结构都被封送,并使用 switch
语句处理每个事件类型。
事件类型
虽然有 11 种事件类型,但此示例应用程序仅实现了 PeopleNearMeChanged
和 RequestStatusChanged
事件。未来的文章将演示其他事件的典型用法。
当协作应用程序在同一子网上的存在状态发生变化时,会触发 PeopleNearMeChanged
事件。可能发生三种类型的变化
- 当本地或远程应用程序登录到“我附近的人”(无服务器存在)时,会发生
Added
。 - 当本地或远程应用程序更改其用户信息(姓名或图片)时,会发生
Updated
。 - 当远程应用程序注销“我附近的人”或本地 Windows 帐户注销时,会发生
Deleted
。
当端点状态引发错误时,会触发 RequestStatusChanged
事件。目前尚不清楚何时以及如何使用此事件。
使用示例应用程序
该示例应用程序仅在 Windows Vista 上运行。它会自动将您登录到“我附近的人”,并向您显示同一子网上的其他人是否也已登录。顶部列表显示了已登录人员的昵称。以下代码显示了如何完成此操作
private void Form_Load(object sender, System.EventArgs e)
{
collab = new PeerCollab();
collab.RequestStatusChanged += new
Peer.Collaboration.PeerCollab.RequestStatusChangedHandler(
collab_RequestStatusChanged);
collab.PeopleNearMeChanged += new
PeerCollab.PeopleNearMeChangedHandler(
collab_PeopleNearMeChanged);
try
{
collab.SignIn(PeerSignInOption.NearMe);
foreach (PeopleNearMe pnm in collab.PeopleNearMe)
{
listBox1.Items.Add(pnm);
}
LogMessage(@"SignIn", "Completed");
}
catch (PeerCollabException ex)
{
LogMessage(@"SignIn", ex.Message);
}
}
单击一个名称以查看有关每个登录到“我附近的人”的应用程序的更多信息。底部列表包含应用程序运行时发生的事件或错误消息的日志。
如果您只有一个 Windows Vista 计算机,请使用“切换用户”功能登录为多个用户。每个用户帐户在子网上都有自己的“我附近的人”状态。您只需在一个帐户下启动示例应用程序。在其他帐户下,打开“控制面板”并使用“我附近的人”登录。您会注意到系统托盘中出现了一个新图标
表示您已登录到“我附近的人”。
表示您已注销。
右键单击这些图标以更改您帐户或其他帐户的存在状态。此外,右键单击并选择“属性”以更改用户信息的昵称。更改名称将导致示例应用程序记录 Updated
事件。以下代码显示了示例应用程序如何处理 PeopleNearMeChanged
事件。
void collab_PeopleNearMeChanged(object sender,
PeerCollabPNMChangedEventArgs e)
{
if (e.PeopleNearMe == null) // its me
{
switch (e.Action)
{
case PeerChangeType.Deleted:
LogMessage(@"SignOut", "Me");
RefreshPeopleNearMe();
break;
case PeerChangeType.Added:
LogMessage(@"SignIn", "Me");
break;
case PeerChangeType.Updated:
LogMessage(@"Update", "Me");
break;
}
}
else
{
switch (e.Action) // its someone else
{
case PeerChangeType.Added:
LogMessage(@"SignIn", e.PeopleNearMe.NickName);
AddPeopleNearMe(e.PeopleNearMe);
break;
case PeerChangeType.Deleted:
// the event does not indicate who was deleted,
// so we are forced to refresh
LogMessage(@"SignOut", "Someone Near Me");
RefreshPeopleNearMe();
break;
case PeerChangeType.Updated:
LogMessage(@"Update", e.PeopleNearMe.NickName);
RefreshPeopleNearMe();
break;
}
}
}
如果 PeopleNearMe
属性为 null
,则该事件是对本地 Windows 帐户的更改;否则,它是针对另一个帐户(可以是本地或远程)。对于 Deleted
操作,事件不包含特定的“我附近的人”引用。因此,示例应用程序通过再次枚举 PeopleNearMe
来刷新其列表。此行为可能会在 Windows Vista 发布时发生变化。
看点
您可以通过访问我的博客来跟踪我在撰写点对点协作文章时的进展。
资源链接
我发现以下资源对于理解点对点协作非常有帮助
结论
希望您觉得本文有趣。在接下来的几周和几个月里,我将撰写更多文章来描述点对点协作的其他功能。
如果您对其他主题有建议,请留言。最后,衷心感谢所有投票的人。
历史
- 初始版本。