Chinaunix首页 | 论坛 | 博客
  • 博客访问: 963962
  • 博文数量: 335
  • 博客积分: 10287
  • 博客等级: 上将
  • 技术积分: 3300
  • 用 户 组: 普通用户
  • 注册时间: 2005-08-08 15:29
文章分类

全部博文(335)

文章存档

2015年(4)

2014年(15)

2013年(17)

2012年(11)

2011年(12)

2010年(96)

2009年(27)

2008年(34)

2007年(43)

2006年(39)

2005年(37)

我的朋友

分类: C/C++

2010-03-11 17:58:37

事件处理及多个IO流:
    如果我们能够通过一种简单的方式,轻松而高效地处理多个事件,这样就可以使应用能够从中极大地受益;事件处理机制常常会采用事件循环(Event Loop)的形式持续地等待事件发生,决定需要采取的动作,把控制分派给处理该事件的其它函数或者方法;
    ACE Reactor框架的设计目标就是实现一种灵活的事件处理机制,使应用无需为了满足事件处理的需要而编写平台相关的代码.使用ACE Reactor框架,应用要实现事件处理,只需要做三件事情:
    A、从ACE_Event_Handler类派生出一个或多个类,并给各个虚回调方法增加应用特有的事件处理代码;
    B、向ACE_Reactor类登记应用的事件处理器对象,把每个事件处理器对象与它感兴趣的事件关联起来;
    C、运行ACE_Reactor类的事件循环ACE_Reactor::instance()->run_reactor_event_loop();
    ACE Reactor框架又称为ACE反应器框架,也叫做反应式模型,其基础是事件多路分离器,比如:select()、poll()、WaitForMultipleObjects(),等系统函数;但是这些系统函数分别用于不同的OS平台上,编写使用这些系统函数的可移植的应用,相当富有挑战性;但是,ACE Reactor框架已经帮我们解决了这样的跨平台问题;
    ACE Reactor框架最常见的用途是处理来自多个来源的IO流; 比如:socket上的IO事件,等等;ACE Reactor框架用于网络编程,是再好不过的了;
    ACE Reactor框架涉及到的类有:
    ACE_Reactor类: 实现Reactor框架的事件处理框架;
    ACE_Event_Handler类: 处理IO流上的事件的事件处理器类,用户要从这个类派生适合自己需要的事件处理器子类;
    每个需要处理任何类型Reactor事件的类,都必须从ACE_Event_Handler类派生;在网络服务器里面,我们可以把"接受连接"的服务和在这个连接上提供服务的"连接服务"分开,分别定义不同的类来实现;这样来安排类,可以很好地体现服务器中发生的事情;在针对一些IO事件向反应器登记某个事件处理器的时候,反应器会把一个ACE_Event_Handler类的指针与一个IO句柄以及该处理器感兴趣的IO事件类型关联在一起;
    ACE隐藏了句柄的概念,但是在基于IO句柄进行事件多路分离的时候,句柄必须得露面;它露面的地方是在ACE_Event_Handler类中,因为IO事件与一个句柄以及一个事件处理器关联在一起.但是ACE封装了该句柄,所以应用类很少需要直接操纵这个句柄;ACE_Event_Handler::get_handle()方法是一个挂钩方法,ACE_Reactor类可以用它来访问所需要的句柄值;
    调用ACE_Reactor::register_handler()方法来向反应器登记事件处理器,该方法在成功时返回0,失败时返回-1;实际上,事件处理器的事件处理函数(事件回调)方法也会在被回调成功时返回0,被回调失败时返回-1;比如,ACE_Event_Handler::handle_input()函数,它在被回调成功时返回0,被回调失败时返回-1;事实上,要想进行正确的Reactor框架编程,任何事件处理器回调函数的返回值都非常重要;
    事件处理器回调函数返回值的含义:
    ==-1: 出错,反应器应该停止检测特定的事件类型,即刚刚在所给定的句柄上分派的事件类型(如果它时IO事件的话).反应器会用句柄值(如果有的话)和正在移除的事件的类型做参数,调用处理器的handle_close()挂钩方法;
    == 0: 没有错误,反应器会继续为刚才分派的处理器和句柄检测所分派的事件;
    >  0: 没有错,与0类似,反应器会继续检测所分派的事件和刚才分派的句柄;但是,在等待其它事件之前,反应器会继续用同样的句柄值回调这个回调方法;如果你知道需要进行多少次IO,那么这是很有用的;但是返回到反应器等待被回调更容易(对其它处理器也更公平);
    处理输入:
    ACE_Event_Handler::handle_input()方法可以用来处理输入;该方法的原型是:
    virtual int handle_input(ACE_HANDLE fd = ACE_INVALID_HANDLE);
    参数是一个ACE封装过的IO句柄;描述的是发生IO事件的对象;handle_input()就是在该句柄上发生了输入事件时被回调的函数,以处理该输入事件;在网络编程中,如果发生输入事件的是一个用于侦听客户端连接请求的socket,那么handle_input()的调用就意味着有客户端连接进来了,需要处理这个连接请求,这种判断可以依据TCP建立连接的三步握手机制得出来; 换句话说,当有客户端连接进来的时候,handle_input()方法就会被回调;如果是用于传输数据的socket,那么,当该socket由于可以接收数据而处于可读状态时,handle_input()函数就会被回调,以接收数据;
    在阻塞式通讯中,当通讯的一端调用close()函数而关闭连接的时候,socket会变得可读,另外一端的recv()调用就会返回,但是recv()函数返回0值,数据字节数为0,这是因为TCP的4路握手断开连接的体现,也是TCP全双工通讯机制决定的;此时,如果recv()返回0值,就表示对方关闭了连接,返回-1,就表示socket失效了;要做好判断;
    在非阻塞式通讯中,这些特点还在,只是在recv()返回0的时候,要检查一下错误码errno是否等于EWOULDBLOCK,如果errno==EWOULDBLOCK,就说明,socket并没有失效,而且正在接收数据,要继续接收数据;相反,如果errno!=EWOULDBLOCK,这才说明socket对方已经关闭了连接,当前socket也要做关闭连接的处理;这一点要注意;
   
处理输出:
    ACE_Event_Handler::handle_output()方法可以用来处理输出;该方法的原型是:
    virtual int handle_output(ACE_HANDLE fd = ACE_INVALID_HANDLE);
    参数是一个ACE封装过的IO句柄;描述的是发生IO事件的对象;handle_output()就是在该句柄上发生了输出事件时被回调的函数,以处理该输出时间;在网络编程中,输出事件就是发送数据时触发的事件;所以,一旦触发了发送数据的操作,handle_output()函数就会被回调,以发送数据;
    注意:如果在socket发送数据时,send()发送失败,但是errno==EWOULDBLOCK,这时的socket并不是真正地失效了,而是无法一次性地把所有数据都发发送出去,或者是暂时无法发送数据,这时,我们就要认真地处理这种情况下的数据发送,我们可以把剩下没有发送出去的数据再次调用发送操作send()进行发送,直到发送完为止;如果socket上发生的真正的错误,那么errno!=EWOULDBLOCK,这个时候才说明socket失效了,我们可以把没有发送出去的数据暂时缓存起来,等待socket重新建立后,重新发送;
    处理初始化:
    该操作在ACE_Event_Handle::open()函数中完成的;可以在这个函数中打开连接、启动侦听、登记实践处理器,等等操作;该函数的原型是:
    int open(const ACE_INET_Addr& addr);
   
处理关闭:
    virtual int handle_close(ACE_HANDLE handle, ACE_Reactor_Mask close_mask);
    该函数会在关闭IO句柄时被回调;参数是需要被关闭的IO句柄和关闭掩码(关闭事件);
    select()和WaitForMultipleObjects()之间的区别:
    A、select()函数是一种"水平触发"机制.事件是基于合乎你需要的状态的"水平线(或状态)"触发的.如果某个socket是可写的,那么,select()函数总是把它记为可写的,直到它不再可写为止;
    B、WaitForMultipleObjects()函数是一种"边缘触发"机制.事件是在当前状态发生变化时被触发的,而不是基于当前状态触发;可写事件在socket从不可写状态变成可写状态时触发的;一经记下,它便不会再次被记下,直到状态再次发生变化为止;socket输入也是类似的,但是调用recv()函数会使状态复位,从而使其再度被激发,即使没有数据到达也仍是如此;因此,这个问题对于数据接收而言没有那么显著;
    这说明,这两者之间的主要区别在于事件的触发方式和触发时间,而且也因为底层的多路分离器的行为和能力不同;
    注意:并不是所有从回调方法中返回的-1都用于指示出错,有时候我们也可以用-1来指示我们完成了数据传输而断开连接;

阅读(837) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~