对等图形 - 记录






4.24/5 (7投票s)
2005 年 12 月 29 日
8分钟阅读

93893

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。
更新记录
发布到图中的记录可以由连接到该图的任何应用程序或用户更新。只能修改四个可写属性:Attributes
、ExpirationTime
、DataAsString
和 DataAsStream
。其他属性,如 ModifiedBy
、LastModifiedTime
和 Version
,由非托管 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 元数据与记录关联。敬请关注以下主题的更多文章
- 对等名称解析 - Windows Vista 增强功能
- 对等图形 - 属性
- 对等图形 - 搜索
- Peer Graph - 导入和导出数据库
- 对等组和身份
- 对等协作 - 附近的人
- 对等协作 - 端点
- 对等协作 - 能力
- 对等协作 - 在线状态
- 对等协作 - 邀请
如果您对其他主题有建议,请留言。
历史
- 初始修订版
许可证
本文未附加明确的许可证,但可能在文章文本或下载文件本身中包含使用条款。如有疑问,请通过下面的讨论区联系作者。
作者可能使用的许可证列表可以在此处找到。