文章出处:
http://msdn.microsoft.com/library/default.asp?url=/library/en-us/winui/winui/windowsuserinterface/userinput/rawinput/aboutrawinput.asp
关于原始输入
除了传统的键盘和鼠标以外还有很多其他的输入设备。例如:用户输入可以来自游戏杆设备,触摸屏,麦克风以及其他可以提供用户输入的设备。这些设备被统称为HID(人体工程学)设备。Raw Input的API为应用程序提供了稳定健壮的读取原始数据数据的方式,包括键盘和鼠标。
这篇文章主要包括3个部分:
· 原始输入模型
· 注册原始输入
· 读取原始输入
原始输入模型
以前鼠标和键盘数据处理模式是这样的,鼠标和键盘产生输入数据,系统中断去处理这些与设备信息相关的数据,让这些数据变得与设备无关。例如:键盘产生与键盘设备相关的ScanCode但是系统提供给应用程序虚拟键码。除了隐藏原始输入的细节,Windows管理器还不支持所有新的HID设备。为了要从HID设备里面得到信息,一个应用程序必须处理以下步骤:打开设备,管理共享模式,周期性读取设备或者设置IO完成端口,或者更多操作。原始输入模型及其相关的API允许比较容易的从输入设备中获取原始输入,包括键盘和鼠标。
那么原始输入模型和微软原来的鼠标键盘输入模型有什么差别呢?在原来的输入模型,一个应用程序通过发送到他窗口的消息获取与设备无关的消息,例如WM_CHAR,WM_MOUSEMOVE和WM_APPCOMMAND。与之原来模式不同的是:一个应用程序想获取原始数据的必须注册他想要获取原始输入的那些设备,应用程序会收到WM_INPUT消息。
原始输入模型有很多优点:
- 应用程序不需要查找和打开输入设备。
- 应用程序直接从设备获取数据并根据他们的需要获取数据。
- 应用程序能够甄别来自两个不同设备的输入,尽管他们有可能是同一个类型的设备。例如:两个鼠标设备。
- 应用程序能够管理数据流,可以从一类设备或者某种制定类型设备获取输入数据。
- 当HID设备在市场上可以买到时候,能够立即被使用,而不需要等待操作系统更新新的消息定义或者操作系统更新WM_APPCOMMAND消息。
需要注意的是:WM_APPCOMMAND确实是为一些HID设备提供的。然而WM_APPCOMMAND是一个高层的非设备相关的输入事件,而WM_INPUT消息发送原始的底层的设备相关的消息。
注册原始输入
默认情况下,没有应用程序会接受WM_INPUT消息。为了接受从一个设备发送原始输入,你必须注册这个设备。
为了注册这个设备,一个应用程序首先必须创建一个指明他所希望接受设备类别的(top level collection――
TLC)RAWINPUTDEVICE结构。TLC被定义成为UsagePage(设备类)和Usage(设备类内的具体设备)。例如为了从键盘获取原始输入,设置UsagePage = 1 and Usage = 6,应用程序调用RegisterRawInputDevice去注册这个设备。
注意:应用程序可以注册系统当前没有的设备。当设备可用之后,Windows管理器会自动将原始输入数据发送到应用程序。应用程序可以调用GetRawInputDeviceList来获取系统中原始输入设备的列表。用GetRawInputDeviceList获取的hDevice,应用程序调用GetRawInputDeviceInfo获取设备信息。
通过RAWINPUTDEVICE中的dwFlag,应用程序可以选择是否监听还是忽略来自某个指定设备的信息。例如:一个应用程序能够监听所有的电话设备除了答录机。
注意:鼠标和键盘也是HID设备,所以能够从Hid设备的WM_INPUT消息或者从传统的消息中获取信息。应用程序能够通过指定RAWINPUTDEVICE中的标志位选择任意一个。
可以调用GetRegisteredRawInputDevice来得到应用程序的该设备的注册状态。
读取原始输入
应用程序会收到符合所注册的TLC的HID设备的原始输入消息。当一个应用程序收到了原始输入,应用程序的消息队列就会得到一个WM_INPUT消息,系统状态被置成QS_RAWINPUT(QS_INPUT)同样包含这个标志。不管应用程序在前台和后台都能够收到消息。
有两种方法去读取原始数据:标准(没有缓冲的)方法和缓冲方法。前者获取原始输入时,每次获取一个RAWINPUT数据,而且对于大多数HID设备都是可以用这种方式读取的。应用程序调用CallMessage得到WM_INPUT消息,然后应用程序通过WM_INPUT消息,调用GetRawInputData来获取RAWINPUT句柄。
相对应的,缓存方式每次得到一系列的RAWINPUT结构。这是给那些能够构产生很大数据量的原始输入。用这种方法去获取数据,首先调用GetRawInputBuffer去获取一系列的RAWINPUT结构。注意NEXTTRAWINPUTBLOCK宏是用来获取下一个RAWINPUT结构的。
为了获取原始输入HID设备的详细信息。应用程序可以用GetRawInputdeviceInfo来查询相对应的句柄。这个句柄可以是从WM_INPUT消息或者RAWINPUTHRADER.hDevice获取。
posted @
2006-09-15 22:12 CPP&&设计模式小屋 阅读(4414) |
评论 (3) |
编辑 收藏
版权所有 未经作者允许 不得用于商业用途 转载请附带这第一,二行
http://www.cppblog.com/shenhuafeng/
Modern C++ Design的第一章就是Policy Based Class Design,可见该技术是整个Loki库的设计基础.这种方式的优点是能够增加程序库的弹性和提高复用性.
简单来说就是,一个Policy Based Class由很多基本的Policy来组成的,每个Policy Class代表了该复杂类(相对复杂)类的某些行为或者特性.有点类似于类的继承,当然和类的继承是不同的。
那么Policy Based Class有什么用呢?我们先看下面这个问题。
假如需要设计一个基础库,可能是基于某个特定领域的,那么库的设计者就需要考虑这样的问题,他需要将未来的可能的需求加以分类,抽象出层次,然后运用OO思想,希望能够构造出一个开发的结构,当然其中的component的设计当然是越是灵活越好。
用传统的OO设计思想,可能可以设计出一套非常完善的类库 ,可能包罗万象。当然的对于应用开发人员需要花很多时间去学习这个“包罗万象”的基础类库。而且往往这样的基础库不是通用性不强就是限制条件太多(例如MFC)可以说就是这样一种类型的库。
下面要展示一下运用多重继承以及Templetes来实现的policy class,举一个简单的例子:
假如我们需要发明一个灯,它有不同的种类,有使用不同能源的工作方式以及有不同的操作方式,如果运用
policy based class来设计的话,可能是这样的结构:
1
templete
<
2
class
T,
3
templete
<
class
>
class
Work,
4
templete
<
class
>
class
OpMethod
5
>
6
class
Light
7
:
public
Work
<
T
>
,
8
OpMethod
<
Light
>
9
10
{
11
T
*
xxOp()
12
{
13
if
(Work(T).Status
==
ACMODE)
14
OpMethod(
*
this
);
15
}
16
}
当你实例化一个Policy Based Class的时候,你还可以给出默认的实现,就像成员函数声明和定义时候给出的默认参数一样。
回头来看一下多重继承和Templetes的特性:
多重继承:欠缺一种一成不变的可以套用的代码,在某种受控的情况下将继承的Class组合起来(多重继承只是将他们放在一起,然后提供一种访问方式)。Templetes:有这样的特性。
多重继承由于继承自多个Base Class,所以型别信息缺乏,而Templetes正式基于型别的。
多重继承容易扩张,而Templetes的特化不容易扩张。
正是两项技术的互补,才使这样的技术实现成为可能。
Light对象继承多个policy class,使得特性得以在编译期间定值,从而实现Light Class功能的扩张。
而Templetes技术使得大部分Work以及OpMethod能够共享大部分基础代码,而对特定的版本实现定值。
这样的好处就是,应用程序开发人员得以在应用设计时期,使用这些Class,选择适合自己的Policy组装自己的代码,从而使得程序大小得以精减,运行速率得以提高,而不必去包含整个又大又全的基础类库。
以上只是一些学习的心得和体会,如果有不对的地方,希望大家多多指教。
posted @
2006-09-13 23:22 CPP&&设计模式小屋 阅读(2520) |
评论 (4) |
编辑 收藏
前段时间刚刚看完STL源代码解析,觉得受益非浅,不过总体来说STL的源代码还是比较浅显易懂的.最近学习Andrei Alexandrescu的Modern C++ Design,随后从网站上下载了Loki库的源代码,发觉还是有些难度的,准备将学习的点滴记录于此,每天写篇学习心得,也算是给自己一个学习的动力吧.
posted @
2006-09-13 11:32 CPP&&设计模式小屋 阅读(5143) |
评论 (0) |
编辑 收藏
Petar Maymounkov and David Mazi`eres
fpetar,dmg@cs.nyu.edu
摘要
本文我们将描述一个在容易出错的网络环境中拥有可证实的稳定性和高性能稳定性的点对点(P2P)系统。我们的系统使用一个很新颖的基于异或运算的拓扑来发送查询并且定位节点, 这简化了算法并且使验证更加容易。 这种拓扑结构具有以下特性,它能通过交换消息传达和加强节点间的有用联系信息。本系统利用这个信息来发送平行的,异步的查询消息来对付节点的失效而不会给用户带来超时时延。
1
.介绍
本论文描述Kademlia , 一个点对点(P2P)的<键, 值>元组存储和查询系统。 Kademlia拥有许多的可喜的特点,这些特点是任何以前的P2P系统所无法同时提供的。它减少了节点必须发送的用来相互认识的配置消息的数量。在做键查询的同时, 配置消息将会被自动传播。 节点拥有足够的知识和灵活性来通过低时延路径发送查询请求。 Kademlia使用平行的,异步的查询请求来避免节点失效所带来的超时时延。通过节点记录相互的存在的算法可以抵抗某些基本的拒绝服务(DoS)攻击。 最后, 仅仅使用在分布式运行时间上较弱的假设(通过对现有点对点系统的测量而确认的这些假设),我们可以正式的证实Kademlia的许多重要特性。
Kademlia
使用了许多点对点(P2P)系统的基本方法。 键是一个160-bit的隐式数量(例如, 对一些大型数据进行SHA-1哈希的值)。 每个参与的机器都拥有一个节点ID, 160位的键。 <键, 值>对将存储在那些ID与键很‘接近’的节点上, 这里‘接近’当然是按照一个接近度的概念来计算的。最后, 一个基于节点ID的路由算法使得任何人可以在一个目的键附近定位到一个服务器。
Kademlia
的许多的优点都是得益于它使用了一个很新颖的方法, 那就是用节点间的键作异或运算的结果来作为节点间的距离。异或运算是对称的, 允许Kademlia的参与者接收来自相同分布的并且包含在其路由表中的节点的查找请求。如果没有这个性质,就像Chord一样,系统无法从它们收到的查询请求中学习到有用的路由信息。更糟的是, 由于Chord中的运算是不对称的, Chord的路由表更加严格。 Chord节点的查找表的每一项都必须存储精确的按ID域的间隔递增的节点。在这个间隔内的任何节点都比这个间隔内的某些键大,因此离键很远。相反,Kademlia 可以在一定的间隔内发送请求给任何节点, 允许基于时延来选择路由,甚至发送平行的,异步的查询。
为了在特定的ID附近定位节点,Kademlia自始至终使用一个单程的路由算法。相反,其它一些系统使用一种算法来接近目标ID,然后在最后的几个跳数使用另外一种算法。在现有系统中,Kademlia与pastry的第一阶段最像,(虽然作者并没有用这种方式来描述),Kademlia 的异或运算可以使当前节点到目标ID的距离粗略的持续减半,以此来寻找节点。在第二阶段,Pastry不再使用距离运算,而是改为比较ID的数字区别。它使用第二种,数字区别运算作为替代。不幸的是,按第二种运算计算的接近比第一种的远得多,这造成特定节点ID值的中断,降低了性能,并且导致在最差行为下的正式分析的尝试失败。
2
.系统描述
每个Kademlia节点有一个160位的节点ID。在Chord系统中,ID是通过某种规则构造出来的,但在这片文章中,为了简化,我们假设每台机器在加入系统时将选择一个随机的160位值。每条节点发送的消息包含它的节点ID, 同时允许接收者记录下发送者的存在信息,如果有必要的话。
键,同样也是160位的标识符。为了发布和寻找<键,值>对,Kademlia依赖一个概念,那就是两标识符之间的距离的概念。给定两个标识符, x和y, Kademlia定义两者的位异或(XOR)的结果作为两者的距离,d(x,y)=x⊕y。我们首先注意到异或运算是一个有意义的运算,虽然不是欧几里得运算。很明显具有下面的性质: d(x,x)=0;如果x≠y, 则d(x, y)>0;任意的x, y: d(x, y) = d(y, x)。 异或运算还满足三角性质:d(x, y) + d(y, z) ≥ d(x, z)。 这个三角性质之所以成立是基于下面这个事实: d(x, z) = d(x, y) + d(y, z); 并且任意的a>=0, b≥0: a+b≥a⊕b。
跟Chord的顺时针循环运算一样,异或运算也是单向的。对于给定的一个点x以及距离Δ,仅有一个点y,使得d(x, y) = Δ。 单向性确保所有对于相同的键的查询将汇聚到相同路径中来,而不管是什么起源节点。因此,在查找路径上缓存<键,值>对可以减少‘撞车’的机会。跟Pastry而不是Chord一样, 异或运算也是对称的。(对所有的x以及y, d(x,y) = d(y,x))
2
.1.节点状态
Kademlia
节点存储互相的联系信息,以用于路由查询消息。对于任何0 =< i < 160, 每个节点保存那些到本节点的距离为2i 到2i+1之间的节点信息列表,包括<IP地址,UDP端口, 节点ID>。我们把这些列表称为K-桶。每个K-桶中的节点按最后联系的时间排序――最久未联系的节点放在头部,最近联系的节点放在尾部。对于比较小的i值,K-桶通常是空的(因为没有合适的节点存在于系统中)。对于比较大的i值,列表节点数可以达到k的大小,k是一个系统级别的冗余参数。k值的选择必须满足一个条件,那就是任意k个节点在一个小时内都失效的可能性很小(例如k =20)。
图1:
以当前已在线时间的函数的形式显示了节点在接下来的一小时后继续在线的比例。X轴代表分钟,y轴代表那些已经在线了x分钟的节点中将继续在线1小时的比例。
当一个Kademlia节点收到来自另外一个节点的任何消息(请求的或者回复的),它将更新自己的一个K-桶,即发送节点ID对应的那个桶。如果发送节点已经存在于接收者的K-桶中,接收者会把它移到列表的尾部。如果这个节点还没有存在于对应的K-桶中并且这个桶少于k个节点,则接收者把发送者插入到列表的尾部。如果对应的K-桶已经满了,则发送者将向该K-桶中的最久未联系节点发送ping命令测试是否存在,如果最久未联系节点没有回复,则把它从列表中移除,并把新的发送者插入到列表尾部。如果它回复了,则新的发送者信息会丢弃掉。
K-
桶非常高效的实现了剔除最久未联系节点的策略,存活的节点将永远不会从列表中移除。这种偏向保留旧节点的做法是我们对由Saroiu等人收集的Gnutella协议的跟踪数据进行分析而得出来的。图1以当前已存在时间的函数的形式显示了Gnutella节点在一小时后继续在线的比例。一个节点存活的时间越长,则这个节点继续存活一小时的可能性越大。通过保留存活时间最长的那些节点,K-桶中存储的节点继续在线的概率大大提高了。
K-
桶的第二个优点是它提供了对一定的拒绝服务(DoS)的攻击的抵抗。系统中不断涌入新节点并不会造成节点路由状态的更新过快。Kademlia节点只有在旧节点离开系统时才会向k-桶中插入新节点。
2
.2.Kademlia协议
Kademlia
协议由4个远程过程调用(RPC)组成:PING,STORE,FIND_NODE, FIND_VALUE。 PING RPC 测试节点是否存在。STORE指示一个节点存储一个<键,值>对以用于以后的检索。
FIND_NODE
把160位ID作为变量,RPC的接收者将返回k个它所知道的最接近目标ID的<IP地址,UDP端口,节点ID>元组。这些元组可以来自于一个K-桶,也可以来自于多个K-桶(当最接近的K-桶没有满时)。在任何情况下, RPC接收者都必须返回k项(除非这个节点的所有的K-桶的元组加起来都少于k个,这种情况下RPC接收者返回所有它知道的节点)
FIND_VALUE
和FIND_NODE行为相似――返回<IP地址,UDP端口,节点ID>元组。仅有一点是不同的,如果RPC接收者已经收到了这个键的STORE RPC,则只需要返回这个已存储的值。
在所有RPC中,接收者都必须回应一个160位的随机RPC ID,这可以防止地址伪造。PING中则可以为RPC接收者在RPC回复中捎回以对发送者的网络地址获得额外的保证。
Kademlia
参与者必须做的最重要的工作是为一个给定的节点ID定位k个最接近节点。我们称这个过程为节点查询。Kademlia使用一种递归算法来做节点查询。查询的发起者从最接近的非空的K-桶中取出а个节点(或者,如果这个桶没有а项,则只取出它所知道的最接近的几个节点)。发起者然后向选定的а个节点发送平行的,异步的FIND_NODE RPC。а是一个系统级别的并行参数,比如为3。
在这个递归的步骤中,发起者重新发送FIND_NODE给那些从上次RPC中学习到的节点(这个递归可以在之前的所有的а个RPC返回之前开始)。在这返回的与目标最接近的k个节点中,发起者将选择а个还没有被询问过的节点并且重新发送FIND_NODE RPC给它们。没有立即作出响应的节点将不再予以考虑除非并且直到它们作出响应。如果经过一轮的FIND_NODE都没有返回一个比已知最接近的节点更接近的节点,则发起者将重新向所有k个未曾询问的最接近节点发送FIND_NODE。直到发起者已经询问了k个最接近节点并且得到了响应,这个查询才结束。当а=1时,查询算法在消息开支和检测失效节点时的时延上与Chord非常相似。 然而,Kademlia可以做到低时延路由因为它有足够的灵活性来选择k个节点中的一个去做查询。
按照上面的查询过程,大多数的操作都可以实现。要存储一个<键,值>对,参与者定位k个与键最接近的节点然后向这些节点发送STORE RPC。另外,每个节点每个小时都会重新发布它所有的<键,值>对。这可以以高概率的把握确保<键,值>对的持续存在于系统中(我们将会在验证概略一节中看到)。通常来说,我们还要求<键,值>对的原始发布者每隔24小时重新发布一次。否则,所有的<键,值>对在最原始发布的24小时后失效,以尽量减少系统中的陈旧信息。
最后,为了维持<键,值>对在发布-搜索生命周期中的一致性,我们要求任何时候节点w拥有一个新节点u,u比w更接近w中的一些<键,值>对。w将复制这些<键,值>对给u并且不从自己的数据库中删除。
为了查找到一个<键,值>对,节点首先查找k个ID与键接近的节点。然而,值查询使用FIND_VALUE而不是FIND_NODE RPC。 而且,只要任何节点返回了值,则这个过程立即结束。为了缓存(caching)的缘故,只要一个查询成功了,这个请求节点将会把这个<键,值>对存储到它拥有的最接近的并且没能返回值的节点上。
由于这个拓扑的单向性,对相同的键的以后的搜索将很有可能在查询最接近节点前命中已缓存的项。对于一个特定的键,经过多次的查找和传播,系统可能在许多的节点上都缓存了这个键。为了避免“过度缓存”,我们设计了一个<键,值>对在任何节点的数据库中的存活时间与当前节点和与键ID最接近的节点ID之间的节点数成指数级的反比例关系。简单的剔除最久未联系节点会导致相似的生存时间分布,没有很自然的方法来选择缓存大小,因为节点不能提前知道系统将会存储多少个值。
一般来说,由于存在于节点之间的查询的通信,桶会保持不停地刷新。为了避免当没有通信时的病态情况,每个节点对在一个小时内没有做过节点查询的桶进行刷新,刷新意味着在桶的范围内选择一个随机ID然后为这个ID做节点搜索。
为了加入到这个网络中,节点u必须与一个已经加入到网络中的节点w联系。u把w加入到合适的桶中,然后u为自己的节点ID做一次节点查找。最后,节点u刷新所有比最接近的邻居节点更远的K-桶。在这个刷新过程中,节点u进行了两项必需的工作:既填充了自己的K-桶,又把自己插入到了其它节点的K-桶中。
3
.验证概述
为了验证我们系统中的特有的函数,我们必须证实绝大多数的操作花费
[
log
n]
+
c
的时间开销,并且c是一个比较小的常数,并且
<
键,值>查找将会以很高的概率返回一个存储在系统中的键。
我们首先做一些定义。对于一个覆盖距离的范围为
[
2
i
,
2
i
+1)
的
K-
桶,定义这个桶的索引号为i。定义节点的深度h为160-i,其中i是最小的非空的桶的索引号。定义在节点x中节点y的桶高度为y将插入到x的桶的索引号减去x的最不重要的空桶的索引号。由于节点ID是随机选择的,因此高度的不统一分布是不太可能的。因此,在非常高的概率下,任意一个给定节点的高度在log n之内,其中n是系统中的节点数。而且,对于一个ID,最接近节点在第k接近的节点中的桶高度很有可能是在常数log k之内。
下一步我们将假设一个不变的条件,那就是每个节点的每个K-桶包含至少一个节点的联系信息,如果这个节点存在于一个合适的范围中。有了这个假设,我们可以发现节点的查找过程是正确的并且时间开销是指数级的。假设与目标ID最接近的节点的深度是h。如果这个节点的h个最有意义的K-桶都是非空的,查询过程在每一步都可以查找到一个到目标节点的距离更接近一半的节点(或者说距离更近了一个bit),因此在
h
-
log
k
步后目标节点将会出现。
如果这个节点的一个K-桶是空的,可能是这样的一种情况,目标节点恰好在空桶对应的距离范围之内。这种情况下,最后的几步并不能使距离减半。然而,搜索还是能正确的继续下去就像键中与空桶相关的那个位已经被置反了。因此,查找算法总是能在
h
-
log
k
步后
返回最接近节点。而且,一旦最接近节点已经找到,并行度会从а扩展到k。寻找到剩下的k-1个最接近节点的步数将不会超过最接近节点在第k接近节点中的桶高度,即不太可能超过log k加上一个常数。
为了证实前面的不变条件的正确性,首先考虑桶刷新的效果,如果不变条件成立。在被刷新后,一个桶或者包含k个有效节点,或者包含在它范围内的所有节点,如果少于k个节点存在的话(这是从节点的查找过程的正确性而得出来的。)新加入的节点也会被插入到任何没有满的桶中去。因此,唯一违反这个不变条件的方法就是在一个特别的桶的范围内存在k+1个活更多的节点,并且桶中的k个节点在没有查找或刷新的干涉下全部失效。然而,k值被精确的选择以保证使所有节点在一小时内(最大的刷新时间)全都失效的概率足够小。
实际上,失败的概率比k个节点在1小时内全都离开的概率小得多,因为每个进入或外出的请求消息都会更新节点的桶。这是异或运算的对称性产生的,因为在一次进入或外出的请求中,与一个给定节点通信的对端节点的ID在该节点的桶范围之内的分布是非常均匀的。
而且,即使这个不变条件在单个节点的单个桶中的确失效了,这也只影响到运行时间(在某些查询中添加一个跳数),并不会影响到节点查找的正确性。只有在查找路径中的k个节点都必须在没有查找或刷新的干涉下在相同的桶中丢失k个节点,才可能造成一次查找失败。如果不同的节点的桶没有重叠,这种情况发生的概率是2-k*k。否则,节点出现在多个其它的节点的桶中,这就很可能会有更长的运行时间和更低概率的失败情况。
现在我们来考虑下<键,值>对的恢复问题。当一个<键,值>对发布时,它将在k个与键接近的节点中存储。同时每隔一小时将重新发布一次。因为即使是新节点(最不可靠的节点)都有1/2的概率持续存活一个小时,一个小时后<键,值>对仍然存在于k个最接近节点中的一个上的概率是1-2-k
。
这个性质并不会由于有接近键的新节点的插入而改变,因为一旦有这样的节点插入,它们为了填充它们的桶将会与他们的最接近的那些节点交互,从而收到附近的它们应该存储的<键,值>对。当然,如果这k个最接近键的节点都失效了,并且这个<键,值>对没有在其它任何地方缓存,Kademlia将会丢失这个<键,值>对。
4
.讨论
我们使用的基于异或拓扑的路由算法与Pastry [1], Tapestry [2]的路由算法中的第一步和 Plaxton的分布式搜索算法都非常的相似。然而,所有的这三个算法,当他们选择一次接近目标节点b个bit的时候都会产生问题(为了加速的目的)。如果没有异或拓扑,我们还需要一个额外的算法结构来从与目标节点拥有相同的前缀但是接下来的b个bit的数字不同的节点找到目标节点。所有的这三个算法在解决这个问题上采取的方法都是各不相同的,每个都有其不足之处;它们在大小为
O
(2
b
log
2
b
n
)
的主表之外
都
另外
需要
一个
大小为
O
(2
b
)
的
次要路由表,这增加了自举和维护的开支,使协议变的更加复杂了,而且对于Pastry和Tapestry来说阻止了正确性与一致性的正式分析。Plaxton虽然可以得到证实,但在像点对点(P2P)网络中的极易失效的环境中不太适应。
相反,Kademlia则非常容易的以不是2的基数被优化。我们可以配置我们的桶表来使每一跳b个bit的速度来接近目标节点。这就要求满足一个条件,那就是任意的
0
< j < 2b
和
0
≤
i <
160
/b
,
在与我们的距离为[j2160-(i+1)b, (j+1)2160-(i+1)b]
的范围内就要有一个桶,这个有实际的项的总量预计不会超过个桶。目前的实现中我们令b=5。
5
.总结
使用了新颖的基于异或运算的拓扑,Kademlia是第一个结合了可证实的一致性和高性能,最小时延路由,和一个对称,单向的拓扑的点对点(P2P)系统。此外,Kademlia引入了一个并发参数,а,这让人们可以通过调整带宽的一个常数参数来进行异步最低时延的跳选择和不产生时延的失效恢复。最后,Kademlia是第一个利用了节点失效与它的已运行时间成反比这个事实的点对点(P2P)系统。
参考文献
[1] A. Rowstron and P. Druschel. Pastry: Scalable, distributed object location and routing for large-scale peer-to-peer systems. Accepted for Middleware, 2001, 2001. http://research.microsoft.com/˜antr/pastry/.
[2] Ben Y. Zhao, John Kubiatowicz, and Anthony Joseph. Tapestry: an infrastructure for fault-tolerant wide-area location and routing. Technical Report UCB/CSD-01-1141, U.C. Berkeley, April 2001.
[3] Andr´ea W. Richa C. Greg Plaxton, Rajmohan Rajaraman. Accessing nearby copies of replicated objects in a distributed environment. In Proceedings of the ACM SPAA, pages 311–320, June 1997.
[4] Stefan Saroiu, P. Krishna Gummadi and Steven D. Gribble. A Measurement Study of Peer-to-Peer File Sharing Systems. Technical Report UW-CSE-01-06-02, University of Washington, Department of Computer Science and Engineering, July 2001.
[5] Ion Stoica, Robert Morris, David Karger, M. Frans Kaashoek, and Hari Balakrishnan. Chord: A scalable peer-to-peer lookup service for internet applications. In Proceedings of the ACM SIGCOMM ’01 Conference, San Diego, California, August 2001.
posted @
2006-09-11 16:18 CPP&&设计模式小屋 阅读(1983) |
评论 (0) |
编辑 收藏
前两天在网上看到世界知名的电骡服务器Razorback 2被查封、4人被拘禁的消息,深感当前做eMule / BitTorrent等P2P文件交换软件的不易。以分布式哈希表方式(DHT,Distributed Hash Table)来代替集中索引服务器可以说是目前可以预见到的为数不多的P2P软件发展趋势之一,比较典型的方案主要包括:CAN、CHORD、Tapestry、Pastry、Kademlia和Viceroy等,而Kademlia协议则是其中应用最为广泛、原理和实现最为实用、简洁的一种,当前主流的P2P软件无一例外地采用了它作为自己的辅助检索协议,如eMule、Bitcomet、Bitspirit和Azureus等。鉴于Kademlia日益增长的强大影响力,今天特地在blog里写下这篇小文,算是对其相关知识系统的总结。
1. Kademlia简述
Kademlia(简称Kad)属于一种典型的结构化P2P覆盖网络(Structured P2P Overlay Network),以分布式的应用层全网方式来进行信息的存储和检索是其尝试解决的主要问题。在Kademlia网络中,所有信息均以的哈希表条目形式加以存储,这些条目被分散地存储在各个节点上,从而以全网方式构成一张巨大的分布式哈希表。我们可以形象地把这张哈希大表看成是一本字典:只要知道了信息索引的key,我们便可以通过Kademlia协议来查询其所对应的value信息,而不管这个value信息究竟是存储在哪一个节点之上。在eMule、BitTorrent等P2P文件交换系统中,Kademlia主要充当了文件信息检索协议这一关键角色,但Kad网络的应用并不仅限于文件交换。下文的描述将主要围绕eMule中Kad网络的设计与实现展开。
2. eMule的Kad网络中究竟存储了哪些信息?
只要是能够表述成为字典条目形式的信息Kad网络均能存储,一个Kad网络能够同时存储多张分布式哈希表。以eMule为例,在任一时刻,其Kad网络均存储并维护着两张分布式哈希表,一张我们可以将其命名为关键词字典,而另一张则可以称之为文件索引字典。
a. 关键词字典:主要用于根据给出的关键词查询其所对应的文件名称及相关文件信息,其中key的值等于所给出的关键词字符串的160比特SHA1散列,而其对应的value则为一个列表,在这个列表当中,给出了所有的文件名称当中拥有对应关键词的文件信息,这些信息我们可以简单地用一个3元组条目表示:(文件名,文件长度,文件的SHA1校验值),举个例子,假定存在着一个文件“warcraft_frozen_throne.iso”,当我们分别以“warcraft”、“frozen”、“throne”这三个关键词来查询Kad时,Kad将有可能分别返回三个不同的文件列表,这三个列表的共同之处则在于它们均包含着一个文件名为“warcraft_frozen_throne.iso”的信息条目,通过该条目,我们可以获得对应iso文件的名称、长度及其160比特的SHA1校验值。
b. 文件索引字典:用于根据给出的文件信息来查询文件的拥有者(即该文件的下载服务提供者),其中key的值等于所需下载文件的SHA1校验值(这主要是因为,从统计学角度而言,160比特的SHA1文件校验值可以唯一地确定一份特定数据内容的文件);而对应的value也是一个列表,它给出了当前所有拥有该文件的节点的网络信息,其中的列表条目我们也可以用一个3元组表示:(拥有者IP,下载侦听端口,拥有者节点ID),根据这些信息,eMule便知道该到哪里去下载具备同一SHA1校验值的同一份文件了。
3. 利用Kad网络搜索并下载文件的基本流程是怎样的?
基于我们对eMule的Kad网络中两本字典的理解,利用Kad网络搜索并下载某一特定文件的基本过程便很明白了,仍以“warcraft_frozen_throne.iso”为例,首先我们可以通过warcraft、frozen、throne等任一关键词查询关键词字典,得到该iso的SHA1校验值,然后再通过该校验值查询Kad文件索引字典,从而获得所有提供“warcraft_frozen_throne.iso”下载的网络节点,继而以分段下载方式去这些节点下载整个iso文件。
在上述过程中,Kad网络实际上所起的作用就相当于两本字典,但值得再次指出的是,Kad并不是以集中的索引服务器(如华语P2P源动力、Razorback 2、DonkeyServer 等,骡友们应该很熟悉吧)方式来实现这两本字典的存储和搜索的,因为这两本字典的所有条目均分布式地存储在参与Kad网络的各节点中,相关文件信息、下载位置信息的存储和交换均无需集中索引服务器的参与,这不仅提高了查询效率,而且还提高了整个P2P文件交换系统的可靠性,同时具备相当的反拒绝服务攻击能力;更有意思的是,它能帮助我们有效地抵制FBI的追捕,因为俗话说得好:法不治众…看到这里,相信大家都能理解“分布式信息检索”所带来的好处了吧。但是,这些条目究竟是怎样存储的呢?我们又该如何通过Kad网络来找到它们?不着急,慢慢来。
4. 什么叫做节点的ID和节点之间的距离?
Kad网络中的每一个节点均拥有一个专属ID,该ID的具体形式与SHA1散列值类似,为一个长达160bit的整数,它是由节点自己随机生成的,两个节点拥有同一ID的可能性非常之小,因此可以认为这几乎是不可能的。在Kad网络中,两个节点之间距离并不是依靠物理距离、路由器跳数来衡量的,事实上,Kad网络将任意两个节点之间的距离d定义为其二者ID值的逐比特二进制和数,即,假定两个节点的ID分别为a与b,则有:d=a XOR b。在Kad中,每一个节点都可以根据这一距离概念来判断其他节点距离自己的“远近”,当d值大时,节点间距离较远,而当d值小时,则两个节点相距很近。这里的“远近”和“距离”都只是一种逻辑上的度量描述而已;在Kad中,距离这一度量是无方向性的,也就是说a到b的距离恒等于b到a的距离,因为a XOR b==b XOR a
5. 条目是如何存储在Kad网络中的?
从上文中我们可以发现节点ID与条目中key值的相似性:无论是关键词字典的key,还是文件索引字典的key,都是160bit,而节点ID恰恰也是160bit。这显然是有目的的。事实上,节点的ID值也就决定了哪些条目可以存储在该节点之中,因为我们完全可以把某一个条目简单地存放在节点ID值恰好等于条目中key值的那个节点处,我们可以将满足(ID==key)这一条件的节点命名为目标节点N。这样的话,一个查找条目的问题便被简单地转化成为了一个查找ID等于Key值的节点的问题。
由于在实际的Kad网络当中,并不能保证在任一时刻目标节点N均一定存在或者在线,因此Kad网络规定:任一条目,依据其key的具体取值,该条目将被复制并存放在节点ID距离key值最近(即当前距离目标节点N最近)的k个节点当中;之所以要将重复保存k份,这完全是考虑到整个Kad系统稳定性而引入的冗余;这个k的取值也有讲究,它是一个带有启发性质的估计值,挑选其取值的准则为:“在当前规模的Kad网络中任意选择至少k个节点,令它们在任意时刻同时不在线的几率几乎为0”;目前,k的典型取值为20,即,为保证在任何时刻我们均能找到至少一份某条目的拷贝,我们必须事先在Kad网络中将该条目复制至少20份。
由上述可知,对于某一条目,在Kad网络中ID越靠近key的节点区域,该条目保存的份数就越多,存储得也越集中;事实上,为了实现较短的查询响应延迟,在条目查询的过程中,任一条目可被cache到任意节点之上;同时为了防止过度cache、保证信息足够新鲜,必须考虑条目在节点上存储的时效性:越接近目标结点N,该条目保存的时间将越长,反之,其超时时间就越短;保存在目标节点之上的条目最多能够被保留24小时,如果在此期间该条目被其发布源重新发布的话,其保存时间还可以进一步延长。
6. Kad网络节点需要维护哪些状态信息?
在Kad网络中,每一个节点均维护了160个list,其中的每个list均被称之为一个k-桶(k-bucket),如下图所示。在第i个list中,记录了当前节点已知的与自身距离为2^i~2^(i+1)的一些其他对端节点的网络信息(Node ID,IP地址,UDP端口),每一个list(k-桶)中最多存放k个对端节点信息,注意,此处的k与上文所提到的复制系数k含义是一致的;每一个list中的对端节点信息均按访问时间排序,最早访问的在list头部,而最近新访问的则放在list的尾部。
k-桶中节点信息的更新基本遵循Least-recently Seen Eviction原则:当list容量未满(k-桶中节点个数未满k个),且最新访问的对端节点信息不在当前list中时,其信息将直接添入list队尾,如果其信息已经在当前list中,则其将被移动至队尾;在k-桶容量已满的情况下,添加新节点的情况有点特殊,它将首先检查最早访问的队首节点是否仍有响应,如果有,则队首节点被移至队尾,新访问节点信息被抛弃,如果没有,这才抛弃队首节点,将最新访问的节点信息插入队尾。可以看出,尽可能重用已有节点信息、并且按时间排序是k-桶节点更新方式的主要特点。从启发性的角度而言,这种方式具有一定的依据:在线时间长一点的节点更值得我们信任,因为它已经在线了若干小时,因此,它在下一个小时以内保持在线的可能性将比我们最新访问的节点更大,或者更直观点,我这里再给出一个更加人性化的解释:MP3文件交换本身是一种触犯版权法律的行为,某一个节点反正已经犯了若干个小时的法了,因此,它将比其他新加入的节点更不在乎再多犯一个小时的罪……-_-b
由上可见,设计采用这种多k-bucket数据结构的初衷主要有二:a. 维护最近-最新见到的节点信息更新;b. 实现快速的节点信息筛选操作,也就是说,只要知道某个需要查找的特定目标节点N的ID,我们便可以从当前节点的k-buckets结构中迅速地查出距离N最近的若干已知节点。
7. 在Kad网络中如何寻找某特定的节点?
已知某节点ID,查找获得当前Kad网络中与之距离最短的k个节点所对应的网络信息(Node ID,IP地址,UDP端口)的过程,即为Kad网络中的一次节点查询过程(Node Lookup)。注意,Kad之所以没有把节点查询过程严格地定义成为仅仅只查询单个目标节点的过程,这主要是因为Kad网络并没有对节点的上线时间作出任何前提假设,因此在多数情况下我们并不能肯定需要查找的目标节点一定在线或存在。
整个节点查询过程非常直接,其方式类似于DNS的迭代查询:
a. 由查询发起者从自己的k-桶中筛选出若干距离目标ID最近的节点,并向这些节点同时发送异步查询请求;
b .被查询节点收到请求之后,将从自己的k-桶中找出自己所知道的距离查询目标ID最近的若干个节点,并返回给发起者;
c. 发起者在收到这些返回信息之后,再次从自己目前所有已知的距离目标较近的节点中挑选出若干没有请求过的,并重复步骤1;
d. 上述步骤不断重复,直至无法获得比查询者当前已知的k个节点更接近目标的活动节点为止。
e. 在查询过程中,没有及时响应的节点将立即被排除;查询者必须保证最终获得的k个最近节点都是活动的。
简单总结一下上述过程,实际上它跟我们日常生活中去找某一个人打听某件事是非常相似的,比方说你是个Agent Smith,想找小李(key)问问他的手机号码(value),但你事先并不认识他,你首先肯定会去找你所认识的和小李在同一个公司工作的人,比方说小赵,然后小赵又会告诉你去找与和小李在同一部门的小刘,然后小刘又会进一步告诉你去找和小李在同一个项目组的小张,最后,你找到了小张,哟,正好小李出差去了(节点下线了),但小张恰好知道小李的号码,这样你总算找到了所需的信息。在节点查找的过程中,“节点距离的远近”实际上与上面例子中“人际关系的密切程度”所代表的含义是一样的。
最后说说上述查询过程的局限性:Kad网络并不适合应用于模糊搜索,如通配符支持、部分查找等场合,但对于文件共享场合来说,基于关键词的精确查找功能已经基本足够了(值得注意的是,实际上我们只要对上述查找过程稍加改进,并可以令其支持基于关键词匹配的布尔条件查询,但仍不够优化)。这个问题反映到eMule的应用层面来,它直接说明了文件共享时其命名的重要性所在,即,文件名中的关键词定义得越明显,则该文件越容易被找到,从而越有利于其在P2P网络中的传播;而另一方面,在eMule中,每一个共享文件均可以拥有自己的相关注释,而Comment的重要性还没有被大家认识到:实际上,这个文件注释中的关键词也可以直接被利用来替代文件名关键词,从而指导和方便用户搜索,尤其是当文件名本身并没有体现出关键词的时候。
8. 在Kad网络中如何存储和搜索某特定的条目?
从本质上而言,存储、搜索某特定条目的问题实际上就是节点查找的问题。当需要在Kad网络中存储一个条目时,可以首先通过节点查找算法找到距离key最近的k个节点,然后再通知它们保存条目即可。而搜索条目的过程则与节点查询过程也是基本类似,由搜索发起方以迭代方式不断查询距离key较近的节点,一旦查询路径中的任一节点返回了所需查找的value,整个搜索的过程就结束。为提高效率,当搜索成功之后,发起方可以选择将搜索到的条目存储到查询路径的多个节点中,作为方便后继查询的cache;条目cache的超时时间与节点-key之间的距离呈指数反比关系。
9. 一个新节点如何首次加入Kad网络?
当一个新节点首次试图加入Kad网络时,它必须做三件事,其一,不管通过何种途径,获知一个已经加入Kad网络的节点信息(我们可以称之为节点I),并将其加入自己的k-buckets;其二,向该节点发起一次针对自己ID的节点查询请求,从而通过节点I获取一系列与自己距离邻近的其他节点的信息;最后,刷新所有的k-bucket,保证自己所获得的节点信息全部都是新鲜的。
posted @
2006-09-11 14:09 CPP&&设计模式小屋 阅读(1360) |
评论 (3) |
编辑 收藏