Reactor事件驱动的两种设计实现:面向对象 VS 函数式编程
Reactor事件驱动的两种设计实现:面向对象 VS 函数式编程这里的函数式编程的设计以muduo为例进行对比说明; Reactor实现架构对比面向对象的设计类图如下: 函数式编程以muduo为例,设计类图如下: 面向对象的Reactor方案设计我们先看看面向对象的设计方案,想想为什么这么做; 从左边开始,事件驱动,需要一个事件循环和IO分发器,EventLoop和Poller很好理解;为了让事件驱动支持多平台,Poller上加一个继承结构,实现select、epoller等IO分发器选用; Channel是要监听的事件封装类,核心成员:fd文件句柄; AcceptChannel和ConnetionChannel派生自Channel,负责实际的网络数据处理;根据职责的不同而区分,AcceptChannel用于监听套接字,接收新连接请求;有新的请求到来时,生成新的socket并加入到事件循环,关注读事件; 比较困难的是用户逻辑层的设计;放在哪里合适? 想想用户与网络库的接口在哪里? EventLooploop; loop.loop(); 用户逻辑层也就只有通过EventLoop与Channel的派生类关联上; UserLogicCallBackcallback; EventLooploop(&callback);//在定义EventLoop时,将callback的指针传入,供后续使用; loop.loop(); 而网络层调用业务层代码时,则通过eventloop_的过渡调用到业务逻辑的函数; eventloop_->getCallBack()->onMessage(this); 函数式编程的Reactor设计函数式编程中,类之间的关系主要通过组合来实现,而不是通过派生实现; 下面再看看各个类的实现; voidChannel::handleEvent() { if(revents_&(POLLIN|POLLPRI|POLLRDHUP)) { if(readCallback_)readCallback_(); } if(revents_&POLLOUT) { if(writeCallback_)writeCallback_(); } } 这样的关键是设置一堆回调函数,通过boost::function()+boost::bind()可以轻松的做到; Acceptor 和TcpConnectionAcceptor类,这个对应到上面的AcceptChannel,但实现不是通过继承,而是通过组合实现; TCPServerTCPServer就是胶水,作用有二:
示例对比通过一个示例来体会这两种实现中回调实现的差别; OO实现channel作为事件的监听接口,加入到事件循环中,当读事件到来时,需要调用 代码层面的实现: intmain() { UserLogicCallBackurlLogic; EventLooploop(urlLogic);//将用户逻辑对象与事件循环对象关联起来 loop.loop(); } callback_用户逻辑层的对象在EventLoop初始化时传入: classEventLoop{ EventLoop(CallBack&callback): callback_(callback) { } CallBack*getCallBack() { return&callback_; } CallBack&callback_;//回调方法基类 } 当读事件到来,在ConnectionChannel中通过eventloop对象作为桥梁,回调消息业务处理onMesssage(); voidConnectionChannel::handleRead(){ intsavedErrno=0; //返回缓存区可读的位置,返回所有读到的字节,具体到是否收全, //是否达到业务需要的数据字节数,由业务层来判断处理 ssize_tn=inputBuffer_.readFd(fd_,&savedErrno); if(n>0) { //通过eventloop作为中介,调用业务层的回调逻辑 loop_->getCallBack()->onMesssage(this,&inputBuffer_); } elseif(n==0) { handleClose(); } else { errno=savedErrno; handleError(); } } 函数式编程实现而muduo的回调,使用boost::function()+boost::bind()实现,通过这两个神器,将使用者和实现者解耦; 以下是时序 代码层面,我们看看用户逻辑层的代码是如何传入的: TcpServerserver_; 在构造函数中,将onMessage传递给TcpServer,这是第一次传递: UserLogicCallBack::UserLogicCallBack(muduo::net::EventLoop*loop,constmuduo::net::InetAddress&listenAddr) :server_(loop,listenAddr,"UserLogicCallBack") { server_.setConnectionCallback( boost::bind(&UserLogicCallBack::onConnection,this,_1)); //这里将onMessage传递给TcpServer server_.setMessageCallback( boost::bind(&UserLogicCallBack::onMessage,_1,_2,_3)); } TcpServer中的相关细节: classTcpServer{ voidsetMessageCallback(constMessageCallback&cb) {messageCallback_=cb;} typedefboost::function<void(constTcpConnectionPtr&,Buffer*,Timestamp)>MessageCallback; MessageCallbackmessageCallback_; }; TcpServer新建连接时,将用户层的回调函数继续往底层传递,这是第二次传递: voidTcpServer::newConnection(intsockfd,constInetAddress&peerAddr) { TcpConnectionPtrconn(newTcpConnection(ioLoop,connName,sockfd,localAddr,peerAddr)); conn->setConnectionCallback(connectionCallback_); //这里将onMessage()传递给TcpConnection conn->setMessageCallback(messageCallback_); conn->setWriteCompleteCallback(writeCompleteCallback_); conn->setCloseCallback(boost::bind(&TcpServer::removeConnection,_1)); ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished,conn)); } 通过这两次传递,messageCallback_作为成员变量保存在TcpConnection中; voidTcpConnection::handleRead(TimestampreceiveTime) { //返回缓存区可读的位置,返回所有读到的字节,具体到是否收全, //是否达到业务需要的数据字节数,由业务层来判断处理 ssize_tn=inputBuffer_.readFd(channel_->fd(),&savedErrno); if(n>0) { //回调业务层的逻辑 messageCallback_(shared_from_this(),&inputBuffer_,receiveTime); } elseif(n==0) { handleClose(); } else { errno=savedErrno; handleError(); } } 完整时序详见最后一节;源代码来自muduo库; 两者的时序图对比Reactor的面向对象编程时序: Reacotr的函数式编程时序: 结论在面向对象的设计中,事件底层回调上层逻辑,本来和loop这个发动机没有任何关系的一件事,却需要使用它来作为中转;EventLoop作为回调的中间桥梁,实在是迫不得已的实现; Posted by: 大CC | 30DEC,2015 (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |