2009年12月28日星期一

Revised P2PSim-0.3 is ready for download now

The original P2PSim ver 0.3 can not be compiled successfully with g++ 4.3. I have fixed the problem and provided the download of my revision on http://www.fosfer.info. The download link can be found within the “Open download” category. Only  the compiling bug is fixed in this revision, while no any other code is touched. So it’s expected to work as same as the original version. If this download violates any copy right, please let me know.

P2PSim-0.3修正版提供下载

我在http://www.fosfer.info上提供了修正后P2PSim-0.3版本的下载。我只是修正了P2PSim 0.3的版本无法在新的G++ 4.3上无法编译通过的问题。该版本的功能实现上未作任何修改,保持了和原版本的一致。下载在该站的Open download下。http://www.fosfer.info目前还在建站中,今后的发展目标是成为我和朋友们的工作室的主页,能提供各位有用的文档、资料以及代码。

2009年12月27日星期日

P2Psim分析笔记(7)-RPC机制

这是分析笔记的最后一篇,这篇介绍了p2p仿真中最常用的RPC机制在P2Psim中的实现。这篇对P2Psim的仿真机制的最后一部分进行了说明。P2Psim中,不同的P2P协议实现都是基于这个机制来模拟peer间的通讯。
    RPC(Remote Procedure call)的本意是,一台电脑通过网络来来执行另外一台电脑上的函数,函数执行的结果再送回本机。这个机制有很多实现和变形,比如xmlrpc, Soap, 我在这里不扯开了,大家google一下就知道了。因此按照RPC的概念,P2P中peer之间的相互访问都可以看作是RPC操作。因此在P2P的仿真中,仿真软件都不约而同实现一套简化了的rpc接口,这样在应用层的p2p协议只要用这套接口就可以不必理会底层网络通信的细节了。这篇中我给出了RPC实现的说明图。
    图中,不同p2p协议通过调用Node中的doRPC()和asyncRPC()来发送数据给远程的peer。前者是同步的,也就是说,在没收到对方peer的回复或者超时前,本peer就一直死等。后者是非同步的rpc调用。在非同步的方式下,peer调用asyncRPC()函数会立刻得到返回,然后协议得到一个该次请求的句柄,可以理解成取货凭证。然后peer隔一段时间在通过rcvRPC()函数,用这个句柄来查询一下,这个远程调用是不是返回结果了,或者失败了(比如网络丢包)。这好比拿着取货凭证去取货一样。同样如果这个peer收到别人发送来的rpc请求,他也要提供处理相应请求的处理函数。当收到这个请求的时候,相应处理函数被调用。关于请求,我们迟点再分析。我们先逐个分析doRPC()和asyncRPC()的实现。
    当Protocol类的对象调用了Node类中的doRPC()时,Node类首先通过_maktrunk函数把这个请求打包(为了更像一个网络报文?不知道),然后依次调用_doRPC_send()函数在这个报文上面加一个channel(libtask里面的多任务间的数据通信机制)把这个请求的trunk发给Network类的send()接口。Network根据拓扑算出这个报文到达对方peer的延迟,然后生成一个NetEvent事件放入事件队列里。当doRPC()发送了这个数据包后,立刻调用_doRPC_receve()来在这个报文的等待这个数据包上的channel上是否有数据过来,如果有,则从channel中取出这个rpc是否成功的信息再依次返回给doRPC()函数和上层的调用代码。如果成功,这个rpc请求中的返回值指针指向的数据结构已经被对方peer给改写了。以这种方式模拟rpc结果数据的返回。
    当Protocol类的对象调用了Node类中的asyncRPC()接口的时候,机制稍微不同一点。asyncRPC同样调用_maktrunk()封装出一个trunk包,然后让_doRPC_send()函数发送出这个包,但是返回的rpc句柄(取货凭证)被存放到_rpcmap结构中暂时保存起来。然后asnycRPC()函数就立刻返回这个句柄给上层protocol的调用代码了。然后呢,上层代码在之后的事件里面可以时不时通过rcvRPC()接口来查询一下_rpcmap结构里面这个凭条对应的请求有没有得到响应或者是否出错了。
   以上讲完了rpc请求发送的基本逻辑流程,接下来说一下rpc请求被接收的流程。上篇我说了,当EventQueue处理NetEvent事件的时候调用其execute()会导致Node中的Packet_handler()接口被调用。这个接口函数会区分,该消息对应的是rpc请求还是rpc应答。如果是应答的话,他就这个报文通过其channel(记得在_doRPC_send()中建立的channel么?)发送过去。 如果_doRPC_receive()正在等待channel上的数据,这个数据就会让_doRPC_receive()返回,完成同步RPC。如果这是一个异步的rpc,无所谓,这个数据就存在channel里面了,等peer调用rcvRPC()去查询和收取。
  如果这是一个请求事件,那Packet_handler()就建立一个新task来运行receive()函数。这个函数中,这个事件中rpc的请求类型会被拿出来分析,对应不同的事件类型,protocol里面的不同事件处理函数会被调用。也就是说Protocol对象实例中的相应RPC处理函数“被”执行了。根据执行结果,receive()函数构造出RPC响应数据包,然后再通过Network类的send()结构最终又变成NetEvent事件插入到事件队列EventQueue里面去了。至于RPC的响应如何处理,上一段已经说过了。

无命名

P2Psim分析笔记(6)- 仿真中的事件机制

    这几天由于P2Psim的分析进度不是很快,所以一直也就没有更新分析笔记,希望大家见谅。在前几篇中,我已经大体介绍了P2Psim的初始化过程。对初始化的分析,到上篇就告一段落。由于我是一边分析一边写笔记,所以有些细节方面可能存在遗漏和错误。如果各位在自己的分析中发现,不妨联系我。在每篇笔记的图片中,有我的邮件地址的水印。

    接着之前的分析,本篇主要介绍了P2Psim的运行机制。这部分的理解,是以后用P2Psim来添加自己的p2p协议的基础。这篇的介绍不涉及太多细节,因为在我的分析中,发现Node类的实现中有太多的细节需要说。所以在这里我只是给大家一个总体的介绍。如果大家去做代码分析,我建议大家分析sillyprotocol,而不要一上手就去看Chord协议。我个人的感觉是,Chord协议涉及太多协议本身的机制,它的机制本身的复杂性,很大程度上会干扰大家对仿真中的消息机制的分析。而sillyprotocol几乎没有什么代码和任何协议机制。因此是一个很好的切入点。

   按照惯例,我给出了分析的简单图例。由于这图是用office的onenote画的,虽然画起来很方便,但是onenote的画图功能实在太弱了,所以不同意义的先只能用颜色区别,而无法用线形来区别,大家只能凑合着看了。

   图中左上角的EventQueue是我介绍的切入点。在之前,我介绍过,他的运行机制就是在不停的调用他的advance()函数从消息队列里面取出事件,然后每次取出消息就去kick一下消息发生器类EventGenerator,然后再对取出的每个event对象,调用其execute()函数来完成相应的操作。那么EventQueue中消息的来源主要是两个: EventGenerator类或其子类;和Node类。

   先看EventGenerator类。这个类在把自己注册到EventQueue实例里面的时候就把仿真结束消息(exit)和各个peer加入仿真的消息(join)加入了队列。前者是SimEvent类型的消息,后者是P2PEvent消息。SimEvent类型的消息只有一个用途,就是结束仿真。P2PEvent事件的用途就是通知各个peer在不同的时候做不同的事情,比如join,crash和lookup这些。不同于其他的仿真软件,如OverSim,P2Psim中P2PEvent事件是由EventGenerator做中心控制的,而不是由各个peer自己控制的。换而言之,EventGenrator控制了全体peer的行为模式。这点简化对于homegeneous的仿真而言是好的,写代码就容易多了。如果要做geterougenous的仿真就困难一点了,不过也不是大问题。

   第二个事件的发生源是Node类。Node类封装peer作为网络结点的角色。对上层p2p协议,Node提供了RPC调用接口。然后把对应的RPC请求和应答转换成网络报文的传输。在仿真中,报文的发送和接受都被转换成NetEvent网络事件。当Node要发网络数据包的时候,他就从Topology对象提供的接口计算出报文到达的延时,然后生成一个报文到达的NetSim事件,放到EventQueue里面,等待EventQueue到时候进行处理。

   介绍完事件的发生源,再说说事件的处理。之前说过,EventQueue在取出Event时候,会调用其execut函数来处理事件。对于不同类型的事件,其execute机制不同。 最简单的是SimEvent的,立刻就通知系统结束仿真了,具体代码各位自己去看。其次是P2PEvent。如果消息对于的操作是EventGenerator让peer join或者crash,他只是把protocol中对应的_alive进行置位和清位。如果是其他操作,如chord中的lookup,这类。他会对应找到protocol对象中的函数调用来完成处理。比如lookup事件, P2PEvent 就把Event中对应的node对象找出来,调用他的lookup()函数.

   如果处理的是NetEvent事件,它的execute会根据事件的类型进行区别对待。之前我提到,Node会把RPC操作转变为网络报文传输,因此NetEvent对应不同网络报文也分两类:请求和应答。如果这个事件对应的是请求,它就让系统为对应的接收结点生成一个新task来跑Node::receive()函数,让node来接收这个“网络数据包”。否则,他就把这个数据包发送到对应的channel去,让等待接收RPC回应的node的()来接收这个回应。

无命名

2009年12月23日星期三

P2Psim分析笔记(5)-EventGenerator and Observer

    本篇笔记,主要是跟踪taskmain()函数中EventGenerateor::parse(event_file)。本篇主要涉及EventGenerator的构造流程,其中对churnEventGenerator类和ChornObserver类进行分析。下面的图示给出了EventGenerateor::parse()函数的调用流程。

    图中EventGenerateor::parse()的调用很简单,首先读取eventfile 里面关于eventgenerator和observer的名字。生成了一个generator实例,然后通过thread()把对应的eventgenerator的任务个激活。最后再生成observer实例。这里我分析了ChurnEventGenerator 和ChurnObserver两个类。按照我的理解Generator主要用于生成对应protocol的node的初始化事件,比如node什么时候开始活过来,以及对eventqueue类实例化和进行激活等操作。observer 的设计本意是用来统计和记录仿真中的各个事件。在P2Psim的设计中,observer的实例化中,observer把自己注册到各个node对象中,这样每次node发消息的时候,在消息队列处理消息的时候,都会把这个消息上的observer拿出来,调用一下他们的kick()来修改observer中的统计信息。

   图中,首先EventGenerator::parse()调用工厂模式生成了eventgenerator实例。我们以churnEventGenerator为例。churnEventGenerator在构造对象的时候,首先读取了所需的参数。图中列出了所有参数,这些参数的具体用途以后再分析(目前我也不清楚,呵呵)。然后实例化了消息队列,并把自己注册为消息队列的observer,这是一个特殊的observer(具体的用途目前也不清楚,估计是每次消息队列处理消息的时候,都能被调用一次)。

  在消息队列的实例化中,消息队列eventqueue,建立了一个特别的channel,然后就调用自己的thread()触发了run()函数变成一个并发任务。run函数首先会在这个channel上等待一个go消息(这个消息会由eventGenerator的实例触发,见图中黄绿色箭头),当收到这个go消息,run()就循环调用advance()函数来处理消息队列里面的每个消息。在处理每个消息的时候,每个消息上的observer会被kick()一下,然后消息队列会执行event上的execute()函数来处理这个消息对应的事务。

      在EventGenerator::parse()生成eventgenerator后,第二件事情是调用了这个实例(churnEventGenerator为例)的thread函数,然后这个消息发生器对象就开始作为一个并发任务开始运行其run()函数了。在其run()函数中,我们可以看到generator的主要工作了:首先生成了一个仿真结束消息,然后从network实例中要来了所有的node信息,把他们都维护在本地的数据结构里面,并把wellknown结点告诉他们,这样他们一开始就能知道他们到那里去找access point。然后设置每个node加入p2p网络的事件。当然,wellknown结点是最早加入的(不然别人找不到他了)。最后给eventqueue发一个go消息,eventqueue就开始处理消息了,整个仿真就开始了(但是由于没有调用yield(),当前任务还没进行切换,所以在物理上那些任务一个都跑不起来)。

    再回到EventGenerator::parse(event_file)函数,最后一步是通过工厂模式建立了observer对象实例。以churnObserver为例,他的工作也很简单,从Network对象实例里面得到所有的node信息,把自己的指针加入他们,这样他们发送消息在消息队列处理时observer就会被触发了。

总结: eventGenerator 是注册在eventQueue上的observer,而普通observer是注册在node上的。

无命名

2009年12月22日星期二

P2Psim分析笔记(4)-Topology 和Network

  在上篇的分析理,taskmain的第三步是让Topology类来分析topology_file,从而启动了Network对象。这次,我主要介绍这部分的详细流程。主要内容包括对Topology类以及子类Euclidian拓扑类的机制分析,Failure Model类以及Network类的交互。搞清楚这些,基本就把P2PSim的启动机制搞清楚了。按照惯例,我在这里给出了所涉及的流程的大体流程图。
无命名
   
    这部分的调用是taskmain函数中的Topology::parse(topology_file)引起的。在pase函数中,首先从topology_file里面读出了topology的名字,例如Euclidian拓扑, 以及对应的failure模型。至于failure模型,好像是用来制定数据传输中的丢包策略的。这个目前不是我的兴趣所在,所以我也懒得去仔细分析了。如果在topology_file里面不指定的话,在这里会默认提供一个无丢包的nullfailure模型。
   在生成topology和failure模型中,他们的构造函数都不会做什么特别的工作。然后这两个对象top和fm被作为参数来构造Network的对象实例。在Network对象的构造中,topology和failure模型会被保存到Network对象里面。以后只能有Network的实例来访问了。最后Network对象构造中会调用thread来让run函数作为一个task来跑。这个函数中,Network实例开了一个channel(libtask中task进行通信的机制),然后就不断从这个channel读取来自topology对象的Node(也就是peer)信息。然后存到自己的_nodes成员中,这个成员是IPaddress 和Node指针的一个map。
  最后,Topology::parse()调用了topology对象的parse完成了topology_file的剩余部分的解析。这里要注意toplogy对象(top指针 )是指向Topology类的一个子类的对象,比如Euclidian类的对象。 接下来,我们拿Euclidian拓扑来举例,这个对象按照
        IPaddress  x,y
的格式来解析peer在空间中的位置,以及用ipaddress来标识peer。解析出每条记录,都通过工厂模式,构造出一个Node类的子类的对象,除了保存在自己的_nodes成员中(也是一个IPaddress 到node的map),通过channel发送给Network对象的run函数。图中的黄绿色箭头表示出这个联系。

2009年12月20日星期日

P2Psim分析笔记(3)-taskmain函数流程

    在上篇里,我们知道P2PSim是基于libtask的,libtask的第一个任务是通过taskmain函数来启动的。换而言之,taskmain是p2psim的主程序。下面的图里面给出了taskmain的主要流程。



    根据图示,taskmain先把-开头的参数解析出来,然后让Node类去解析protocol_file,然后让Topology类去负责解析topology_file。Topology在解析topology_file后调用Network类生出了第一个并发任务,这个任务负责根据topology建立peer的vector。随后调用了yield放弃cup让Network任务去充分处理topology对应的Peer生成工作。
    然后,taskmain让EventGenerator类根据event_file生成了对应的事件发生器(EventGenerator)的对象,这里采用了工厂模式,根据配置文件生成对应的事件发生器。同时还生成对应的观察者对象。 生成的发生器对象也是一个从Threaded派生的家伙,因此此刻第三个并发任务也开始运行了。
    最后,taskmain遍历了所有的node对象,让他们完成初始化。随后完成所有工作退出。