P2psim源代码分析三
Kejieleung
继续上一篇事件发生器EventGenerator的分析,这部分重点分析事件队列EventQueue的设计。事件队列,作为Observer模式中的ConcreteSubject,首先复习一下Observer模式吧。
可参考:
http://www.cppblog.com/zliner/archive/2006/09/10/12217.html
有一篇简要介绍
观察者模式把目标对观察者的依赖进行抽象:使目标只知道自己有若干观察者,但不知道这些观察者具体是谁,可能有多少个;当目标状态改变时只要给这些观察者一个通知,不必作更多的事情。这样目标对观察者的依赖就达到了抽象和最小,而目标对具体观察者的依赖被解除了。
类图如下:
Subject对象保存一个Observer引用的列表,当我们让一个ConcreteObserver对象观察Subject对象时,调用后者的Attach()方法,将前者的引用加入该列表中。当Subject对象状态改变时,它调用自身的Notify方法,该方法调用列表中每一个Observer的Update()方法。一个ConcreteObserver只要重定义Update()就能收到通知,作为对通知的响应,Update()调用Subject对象的getStatus()获取数据,然后更新自身。当不需要继续观察时,ConcreteObserver对象调用Subject对象的Detach()方法,其引用被从列表中移除。
OK看完上面的部分后,即可看一下EventQueue的观察者模型的实现。可看下面类图
Observerd对应的就是Subject了,EventQueue对应ConcreteSubject,按照观察者模式,首先目标对象保存观察者对像,这部分的是由具体的实现ChurnEventGenerator的构造函数里调用:
- //kejie: 注册监视器
- EventQueue::Instance()->registerObserver(this);
- EventQueue的完整class声明:
- class EventQueue : public Threaded, public Observed {
- friend class EventQueueObserver;
- public:
- static EventQueue* Instance(); //singletion
- ~EventQueue();
- void add_event(Event*);
- Time time() { return _time; }
- static Time fasttime() { return _instance?_instance->time():0; }
- void go();
- private:
- EventQueue();
- struct eq_entry {
- eq_entry() { ts = 0; events.clear(); }
- eq_entry(Event *e) { ts = e->ts; events.clear(); }
- Time ts;
- vector<Event*> events;
- sklist_entry<eq_entry> _sortlink;
- };
- skiplist<eq_entry, Time, &eq_entry::ts, &eq_entry::_sortlink> _queue;
- static EventQueue *_instance;
- Time _time;
- Channel *_gochan;
- virtual void run(); //kejie:线程函数
- bool advance();
- // for debuging
- void dump();
- };
EventQueue这部分实现又使用了singleton单件模式,在第一次使用时实例化:
EventQueue*
EventQueue::Instance()
{
if(_instance)
return _instance;
return (_instance = New EventQueue());
}
EventQueue的构造函数再创建一个底层channel和启动线程,关于channel模型的分析会放在后,这里先理解为底层线程队列的实现即可。注意这里的事件都是能过一个skiplist的数据结构来保存,具体实现在./p2psim/skiplist.h,因为是使用了template所以声明实现都放在这个文件里了。
EventQueue::EventQueue() : _time(0)
{
//kejie: Channel *_gochan;
_gochan = chancreate(sizeof(Event*), 0);
assert(_gochan);
//kejie:add self run to the threadmanage
thread();
}
这里最主要的就是run函数了,本身也作为一个总线程的线程函数,当EventQueue有多于一个事件到达时,就会调用advance()处理,这是一个线程触发函数,将事件分发到另一个线程中处理。
- //kejie:线程函数
- void
- EventQueue::run()
- {
- // Wait for threadmain() to call go().
- recvp(_gochan);
- while(true) {
- // let others run
- while(anyready())
- yield();
- // time is going to move forward.
- // everyone else is quiet.
- // must be time for the next event.
- // run events for next time in the queue
- if(!advance())
- break;
- }
- }
- // moves time forward to the next event
- bool
- EventQueue::advance()
- {
- ...
- for(vector<Event*>::const_iterator i = eqe->events.begin(); i != eqe->events.end(); ++i) {
- assert((*i)->ts == eqe->ts &
- (*i)->ts >= _time &
- (*i)->ts < _time + 100000000);
- // notify observers, who will not add eventsinto the eventqueue using EventQueueObserver::add_event
- notifyObservers((ObserverInfo*) *i);
- Event::Execute(*i); // new thread, execute(), delete Event
- ...
- }
在有事件到达时,通知EventQueueObserver更新状态,调用Observerd的kick函数
void
Observed::notifyObservers(ObserverInfo *oi)
{
if(!_hasObservers)
return;
for(set<Observer*>::const_iterator i = _observers.begin(); i != _observers.end(); ++i)
(*i)->kick(this, oi);
}
kick函数是 Observer的纯虚函数(Update),通知所有Observer更新状态,为调用具本的实现函:ChurnEventGenerator::kick(Observed *o, ObserverInfo *oi)作相应的处理。
在ChurnEventGenerator处理三种基本的事类型:join,crash,lookup
最后分析一下ChurnEventGenerator::run()
事件发生器,整个模拟都是通这是里发出的事件来推动 join/depatrture/die,继承自 Threaded 的虚函数,以线程方式运行(包装好的底层task)。通过预先随机计算所有结点的join,lookup 事件,将其加入到EventQueue中,crash事件是在lookup失效(指定失效概率)后再加到EventQueue的。在计算完成后,才调用EventQueue::Instance()->go();启动
- void
- ChurnEventGenerator::run()
- {
- ...
- vector<IPAddress> *_ips = Network::Instance()->getallfirstips();
- IPAddress ip = 0;
- for(u_int xxx = 0; xxx < _ips->size(); xxx++){
- ip = (*_ips)[xxx];
- Args *a = New Args();
- (*a)["wellknown"] = _wkn_string; //每个结点都要设置引导结点
- (*a)["first"] = _wkn_string; //a hack //first lookup node
- u_int jointime;
- ...
- if( now() + jointime < _exittime ) {
- P2PEvent *e = New P2PEvent(now() + jointime, ip, "join", a); //kejie:生成单个P2P事件
- add_event(e); //加入事件队列
- } else {
- delete a;
- a = NULL;
- }//end if
- ...
- if( _lookupmean > 0 && now() + jointime + tolookup < _exittime ) {
- P2PEvent *e = New P2PEvent(now() + jointime + tolookup, ip, "lookup", a); //kejie:查找事件
- add_event(e);
- } else {
- delete a;
- }//end if
- }//end for
EventQueue::Instance()->go(); //kejie:确定好所有结点的加入/查找事件时间,就可以启动事件运行.