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

Microsoft 的点对点组播技术介绍

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.80/5 (18投票s)

2005 年 11 月 9 日

9分钟阅读

viewsIcon

97179

downloadIcon

1029

微软点对点图形技术介绍。

背景

本文介绍了微软的点对点图形技术。图形为 Windows 点对点应用程序提供了一个稳定、可靠且健壮的通信基础设施。图形是连接对等方、服务和资源在对等网络中的基础。对等名称解析协议 (PNRP - 无服务器 DNS) 用于在图形中注册和发现对等方。

一个图形由对等方(有时称为节点)组成。对等方可以是用户交互式应用程序、服务或资源。图形允许数据在对等方之间高效可靠地传递。通常,数据会在整个图形中广播并与所有对等方共享。然而,一个对等方可以与图形中的任何其他对等方建立直接连接以交换私有数据。

引言

微软的整个点对点技术通过最新的 Platform SDK 以 C/C++ API 调用形式公开。然而,本文中的代码展示了如何使用 C# 从 .NET 托管代码调用这些 API。示例应用程序包含一个 `PeerGraph` 类,该类实现了创建、打开和删除图形的方法。此外,还提供了委托来拦截图形中的更改。虽然 `PeerGraph` 类隐藏了使用微软点对点图形 API 的细节(以简化编程模型),但下面概述了非托管调用的流程。

启动

在调用任何其他点对点图形 API 之前,必须调用 `PeerGraphStartup` 来初始化图形。同样,在应用程序退出之前,必须调用 `PeerGraphShutdown` 以清除其在图形中的存在。为确保这一点,提供了一个静态单例类。`PeerGraph` 类包含一个引用,以确保在其他图形 API 使用之前创建该单例。因此,您不必记住这一点。以下代码显示了单例类

sealed class PeerGraphService
{
  private PeerGraphService()
  {
    PEER_VERSION_DATA data;
    uint hr = PeerGraphNative.PeerGraphStartup(1, out data);
    if (hr != 0) throw new PeerGraphException(hr);
  }
  ~PeerGraphService()
  {
     PeerGraphNative.PeerGraphShutdown();
  }
  public static readonly PeerGraphService Instance = 
                             new PeerGraphService();
  private static readonly Peer.NameResolution.WSAService 
          service = Peer.NameResolution.WSAService.Instance;
}

注意最后一行如何也负责调用 PNRP Service 单例,以确保 WSAxxx 子系统已初始化。

PeerGraph 类

`PeerGraph` 类的目的是公开所有对等图形功能。然而,本文开头的代码下载只实现了创建、打开和删除图形的功能,并为点对点图形触发的一些事件提供了事件处理程序。

创建图形

使用图形的第一步是创建一个图形。图形只需要由第一个发布它的对等方创建一次。可以把它想象成创建一个 Windows 域,但没有中央服务器。

静态 `Create` 方法接受三个参数:图形名称、身份和数据库名称。图形名称是描述图形用途的名称。数据库名称参数指示用于缓存发布到图形的数据的内部数据库的名称。此数据库位于“Documents and Settings\<User Name>\Application Data\PeerNetworking”下。身份参数是与连接到图形的应用程序或用户关联的名称。与身份匹配的子文件夹包含此身份创建的每个图形数据库的子文件夹。身份是一个图形概念,与您的 Windows 用户帐户无关。以下代码显示了 `Create` 方法

public static void Create(string GraphName, 
                   string Identity, string DatabaseName)
{
  PeerGraphProperties properties = new PeerGraphProperties(GraphName);
  properties.SetIdentity(Identity);

  PEER_GRAPH_PROPERTIES props = properties.Convert();
  IntPtr hGraph;
  uint hr = PeerGraphNative.PeerGraphCreate(ref props, 
               DatabaseName, IntPtr.Zero, out hGraph);
  if (hr != 0) throw new PeerGraphException(hr);

  hr = PeerGraphNative.PeerGraphClose(hGraph);
  if (hr != 0) throw new PeerGraphException(hr);
}

在创建图形时(包括名称和身份),使用 `PEER_GRAPH_PROPERTIES` 类来定义设置。这些属性与 `DatabaseName` 一起传递给底层 `PeerGraphCreate` 方法。如果本地已存在数据库副本,此方法可能会失败。

删除数据库

`PeerGraph` 类的静态 `Delete` 方法允许您删除给定身份的本地数据库。

public static void Delete(string DatabaseName, string Identity)
{
  uint hr = PeerGraphNative.PeerGraphDelete(properties.GraphName, 
                                         Identity, DatabaseName);
  if (hr != 0) throw new PeerGraphException(hr);
}

调用 `PeerGraphDelete` 方法以删除包含发布到图形的数据缓存副本的本地数据库。这不会删除其他对等方上的图形,并且严格来说是本地操作。

打开图形

`PeerGraph` 构造函数需要图形的名称。该名称将转换为一个不安全的对等名称,用于通过 PNRP 在 Global_ 云中广播图形的存在。

`Open` 方法需要一个数据库名称和身份。以下代码显示了 `Open` 方法

public void Open(string DatabaseName, string Identity)
{
  if (hGraph != IntPtr.Zero) Close();

  properties.SetIdentity(Identity);
  uint hr = PeerGraphNative.PeerGraphOpen(properties.GraphName, 
            Identity, DatabaseName, IntPtr.Zero, 0, 
            IntPtr.Zero, out hGraph);
  if (hr != 0 && hr != PeerGraphNative.PEER_S_GRAPH_DATA_CREATED)
      throw new PeerGraphException(hr);

  RegisterForEvents();
  Connect(true);
}

使用图形名称和身份以及 `DatabaseName` 参数调用底层 `PeerGraphOpen` 方法。第一次打开图形时,会创建一个新数据库,否则使用现有数据库。接下来,`Open` 方法注册事件(见下文)并连接到图形。连接代码如下。

private void Connect(bool Listen)
{
  IPEndPoint endpoint = DiscoverAddress();
  if (endpoint != null)
  {
    PEER_ADDRESS address = new PEER_ADDRESS();
    address.dwSize = Marshal.SizeOf(typeof(PEER_ADDRESS));
    address.sin6.sin6_addr = endpoint.Address.GetAddressBytes();
    address.sin6.sin6_family = (short)endpoint.AddressFamily;
    address.sin6.sin6_port = (ushort)endpoint.Port;

    uint hr = PeerGraphNative.PeerGraphConnect(hGraph, 
              IntPtr.Zero, ref address, out connectionId);
    if (hr != 0) throw new PeerGraphException(hr);
  }
  else if (Listen)
  {
    this.Listen();
  }
}

在连接之前,本地对等点会通过 PNRP 检查此图是否已注册(使用内部 `DiscoverAddress` 方法)。如果找到了另一个对等点,则将地址传递给 `PeerGraphConnect` 方法,该方法连接并同步当前图的状态与另一个对等点。此操作是异步的。返回的 `connectionId` 输出将在稍后 `ConnectionChanged` 事件触发时使用,以指示同步已完成。同步完成后,`ConnectionChanged` 事件处理程序会在内部注册 PNRP,并开始侦听其他对等点连接并使用该图。

如果无法定位其他对等点,则本地对等点会向 PNRP 注册并开始侦听其他对等点连接并使用该图形。

监听

以下代码显示了内部 `Listen` 方法

private void Listen(PeerGraphScope Scope, int ScopeId, short Port)
{
  uint hr = PeerGraphNative.PeerGraphListen(hGraph, Scope, ScopeId, Port);
  if (hr != 0) throw new PeerGraphException(hr);

  // register peer name so others can connect to graph
  RegisterAddress();
}

Listen 调用 `PeerGraphListen` 方法,该方法在范围内(默认 Global_)打开一个动态端口。请注意,如果启用了 Windows 防火墙,会弹出一个消息框,询问您是否要继续阻止该应用程序。假设您解除阻止该应用程序,它将发布图形并开始侦听来自其他对等方的传入事件。

为了让其他对等方定位图形,内部 `RegisterAddress` 方法将图形注册为 PNRP 的对等名称(在此示例中为 `0.TestGraph`)。

事件

图形 API 允许应用程序在图形中发生变化时注册并接收事件。以下代码显示了内部 `RegisterForEvents` 方法如何使用 `PeerGraphRegisterEvent` 方法来注册事件。

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

  PEER_GRAPH_EVENT_REGISTRATION[] info = 
  {
    new PEER_GRAPH_EVENT_REGISTRATION(
        PEER_GRAPH_EVENT_TYPE.PEER_GRAPH_EVENT_STATUS_CHANGED),
    new PEER_GRAPH_EVENT_REGISTRATION(P
        EER_GRAPH_EVENT_TYPE.PEER_GRAPH_EVENT_PROPERTY_CHANGED),
    new PEER_GRAPH_EVENT_REGISTRATION(
        PEER_GRAPH_EVENT_TYPE.PEER_GRAPH_EVENT_RECORD_CHANGED),
    new PEER_GRAPH_EVENT_REGISTRATION(
        PEER_GRAPH_EVENT_TYPE.PEER_GRAPH_EVENT_DIRECT_CONNECTION),
    new PEER_GRAPH_EVENT_REGISTRATION(
        PEER_GRAPH_EVENT_TYPE.PEER_GRAPH_EVENT_NEIGHBOR_CONNECTION),
    new PEER_GRAPH_EVENT_REGISTRATION(
        PEER_GRAPH_EVENT_TYPE.PEER_GRAPH_EVENT_INCOMING_DATA),
    new PEER_GRAPH_EVENT_REGISTRATION(
        PEER_GRAPH_EVENT_TYPE.PEER_GRAPH_EVENT_CONNECTION_REQUIRED),
    new PEER_GRAPH_EVENT_REGISTRATION(
        PEER_GRAPH_EVENT_TYPE.PEER_GRAPH_EVENT_NODE_CHANGED),
    new PEER_GRAPH_EVENT_REGISTRATION(
        PEER_GRAPH_EVENT_TYPE.PEER_GRAPH_EVENT_SYNCHRONIZED)
  };

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

  int offset = 0;
  foreach (PEER_GRAPH_EVENT_REGISTRATION item in info)
  {
    Marshal.StructureToPtr(item, (IntPtr)(infoptr.ToInt32()+offset), false);
    offset += size;
  }

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

  uint result = PeerGraphNative.PeerGraphRegisterEvent(hGraph, 
                sendEvent.Handle, info.Length, infoptr, out hPeerEvent);
  if (result != 0) Marshal.ThrowExceptionForHR((int)result);
}

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

`WaitOrTimerCallback` 表示当图形中发生变化时执行的回调方法。`RegisterWaitForSingleObject` 使用无限超时将此委托注册到默认线程池。最后,事件句柄和数据结构被传递给 `PeerGraphRegisterEvent`

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

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

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

    switch (data.eventType)
    {
      case PEER_GRAPH_EVENT_TYPE.PEER_GRAPH_EVENT_STATUS_CHANGED:
        HandleEventStatusChanged(dataptr);
        break;
    //...
    }
    PeerGraphNative.PeerGraphFreeData(evptr);
  }
}

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

事件类型

以下列表总结了事件类型及其含义。

  • StatusChanged - 指示对等方在图中的状态(已同步、正在监听、有连接)。
  • PropertyChanged - 图形的一个或多个属性已更改。
  • RecordChanged - 发布到图形的数据已更改。
  • ConnectionChanged - 到远程对等方或直接连接的连接已更改(已连接、已断开、失败)。
  • IncomingData - 已从与另一个对等方的直接连接接收到私有数据。
  • NodeChanged - 对等方的存在已更改(已连接、已断开连接、IP 地址已更新)。
  • EventSynchronized - 指示记录类型已同步。

示例应用程序仅实现了 `StatusChanged` 事件。未来的文章将演示如何通常使用其他事件。

使用示例应用程序

该示例应用程序允许您使用初始身份创建图形(不安全的对等名称 `0.TestGraph`)。第一次打开图形时必须使用此身份。之后,可以使用其他身份,前提是图形正在其他计算机上运行。`Delete` 按钮允许您删除该身份的数据库。请务必在删除之前 `Close` 图形,否则会显示数据库文件正在使用中的错误。

创建后,您可以在同一台计算机上或网络上的不同计算机上运行应用程序的多个副本。输入一个身份,然后单击“打开”按钮打开图形。请注意,在同一台计算机上运行应用程序时,在打开之前必须使用不同的身份,否则 `Open` 将因“未指定错误”而失败。打开后,状态栏显示本地对等方的当前状态。最后,列表框记录每个操作的结果。虽然不是非常令人兴奋,但它展示了构建点对点图形应用程序的第一步。

下一篇文章将更深入地探讨监控和与图中的其他对等方交互,以及处理用于传递私有数据的直接连接。

看点

通过让所有运行中的对等方向 PNRP 注册图形名称,新的对等方可以轻松定位连接到同一图形的其他运行中的对等方。换句话说,图形代表资源或服务,并由一个不安全的对等名称表示。每个向 PNRP 注册的对等方实际上都在发布其在图形中的参与。这使得新的对等方可以轻松定位运行中的对等方,连接到服务或资源,并反过来发布其在图形中的参与。

值得一提的一个问题是,任何人都可以注册一个不安全的对等名称。一旦黑客知道了与图形关联的不安全的对等名称,就有可能创建一个对等方来欺骗或干扰发布到图形的数据或连接到图形的对等方。这限制了图形的使用范围,只能用于本地计算机上的进程间通信和共享状态,或者用于可以更严格控制网络安全访问的企业网络。

微软为更广泛的互联网通信创建了点对点组。对等组使用基于证书的安全对等命名方案。对等方必须经过邀请才能加入并与其他对等方在组中进行通信。但这将是另一篇文章的主题。

资源链接

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

结论

希望您觉得本文有用。我正在考虑撰写更多关于以下主题的文章,以进一步加深您对微软点对点技术的理解

  1. 对等名称解析 - Windows Vista 增强功能
  1. 对等图形 - 节点和连接
  2. 对等图形 - 记录
  3. 对等图形 - 属性
  4. 对等图形 - 搜索
  1. 对等组和身份
  1. 对等协作 - 附近的人
  2. 对等协作 - 端点
  3. 对等协作 - 能力
  4. 对等协作 - 在线状态
  5. 对等协作 - 邀请

如果您对其他主题有任何建议,请留言。最后,衷心感谢所有投票的朋友。

历史

初始版本。

© . All rights reserved.