Reactor事件驱动的两种设计实现:面向对象 VS 函数式编程
內(nèi)容目錄:
- Reactor實(shí)現(xiàn)架構(gòu)對(duì)比
- 面向?qū)ο蟮腞eactor方案設(shè)計(jì)
- 函數(shù)式編程的Reactor設(shè)計(jì)
- 示例對(duì)比
- 兩者的時(shí)序圖對(duì)比
- 結(jié)論
Reactor事件驅(qū)動(dòng)的兩種設(shè)計(jì)實(shí)現(xiàn):面向?qū)ο?VS 函數(shù)式編程
這里的函數(shù)式編程的設(shè)計(jì)以muduo為例進(jìn)行對(duì)比說(shuō)明;
Reactor實(shí)現(xiàn)架構(gòu)對(duì)比
面向?qū)ο蟮脑O(shè)計(jì)類圖如下:
?
函數(shù)式編程以muduo為例,設(shè)計(jì)類圖如下:
?
面向?qū)ο蟮腞eactor方案設(shè)計(jì)
我們先看看面向?qū)ο蟮脑O(shè)計(jì)方案,想想為什么這么做;?
拿出Reactor事件驅(qū)動(dòng)的模式設(shè)計(jì)圖,對(duì)比來(lái)看,清晰明了;
??
從左邊開始,事件驅(qū)動(dòng),需要一個(gè)事件循環(huán)和IO分發(fā)器,EventLoop和Poller很好理解;為了讓事件驅(qū)動(dòng)支持多平臺(tái),Poller上加一個(gè)繼承結(jié)構(gòu),實(shí)現(xiàn)select、epoller等IO分發(fā)器選用;
Channel是要監(jiān)聽的事件封裝類,核心成員:fd文件句柄;?
成員方法圍繞著fd展開展開,如關(guān)注fd的讀寫事件、取消關(guān)注fd的讀寫事件;?
核心方法:?
enableReading/Writing;?
disableReading/Writing;?
以及事件到來(lái)后的處理方法:?
handleEvent;?
在OO設(shè)計(jì)這里,handleEvent設(shè)計(jì)成一個(gè)虛函數(shù),回調(diào)上層實(shí)際的數(shù)據(jù)處理;
AcceptChannel和ConnetionChannel派生自Channel,負(fù)責(zé)實(shí)際的網(wǎng)絡(luò)數(shù)據(jù)處理;根據(jù)職責(zé)的不同而區(qū)分,AcceptChannel用于監(jiān)聽套接字,接收新連接請(qǐng)求;有新的請(qǐng)求到來(lái)時(shí),生成新的socket并加入到事件循環(huán),關(guān)注讀事件;?
ConnetionChannel用于真實(shí)的用戶數(shù)據(jù)處理,處理用戶的讀寫請(qǐng)求;涉及到具體的數(shù)據(jù)處理,當(dāng)然,在這里會(huì)需要用到應(yīng)用層的緩存區(qū);
比較困難的是用戶邏輯層的設(shè)計(jì);放在哪里合適??
先看看需求,用戶邏輯層需要知道的事件點(diǎn)(在這之后可能會(huì)有應(yīng)用層的邏輯):?
連接建立、消息到來(lái)、消息發(fā)送完畢、連接關(guān)閉;?
這四個(gè)事件的源頭是Channel的handleEvent(),直接調(diào)用者應(yīng)該Channel的派生類(AcceptChannel和ConnetionChannel),貌似可以將用戶邏輯層的指針?lè)诺紺hannel里;?
且不說(shuō)架構(gòu)上是否合理,單是實(shí)現(xiàn)上右邊Channel這一塊(含AcceptChannel和ConnetionChannel)對(duì)用戶是透明的,用戶只需要關(guān)注以上四個(gè)事件點(diǎn),底層的細(xì)節(jié)用戶層并不關(guān)心(比如是否該在事件循環(huán)中關(guān)注某個(gè)事件,取消關(guān)注某個(gè)事件,對(duì)用戶都是透明的),所以外部用戶無(wú)法直接將用戶邏輯層的指針給Channel;
想想用戶與網(wǎng)絡(luò)庫(kù)的接口在哪里??
IO分發(fā)器對(duì)用戶也是透明的,用戶可見就是EventLoop,在main方法中:
用戶邏輯層也就只有通過(guò)EventLoop與Channel的派生類關(guān)聯(lián)上;?
這樣,就形成的最終的設(shè)計(jì)類圖,在main方法中:
而網(wǎng)絡(luò)層調(diào)用業(yè)務(wù)層代碼時(shí),則通過(guò)eventloop_的過(guò)渡調(diào)用到業(yè)務(wù)邏輯的函數(shù);?
比如ConnetionChannel中數(shù)據(jù)到達(dá)的處理:
函數(shù)式編程的Reactor設(shè)計(jì)
函數(shù)式編程中,類之間的關(guān)系主要通過(guò)組合來(lái)實(shí)現(xiàn),而不是通過(guò)派生實(shí)現(xiàn);?
整個(gè)類圖中僅有Poller處使用了繼承關(guān)系;其它的都沒(méi)有使用;?
這也是函數(shù)式編程的一個(gè)設(shè)計(jì)理念,更多的使用組合而不是繼承來(lái)實(shí)現(xiàn)類之間的關(guān)系,而支撐其能夠這樣設(shè)計(jì)的根源在于function()+bind()帶來(lái)的函數(shù)自由傳遞,實(shí)現(xiàn)回調(diào)非常簡(jiǎn)單;?
而OO設(shè)計(jì)中,只能使用基于虛函數(shù)/多態(tài)來(lái)實(shí)現(xiàn)回調(diào),不可避免的使用繼承結(jié)構(gòu);
下面再看看各個(gè)類的實(shí)現(xiàn);?
事件循環(huán)EventLoop和IO分發(fā)器沒(méi)有區(qū)別;?
Channel的職責(zé)也和上面類似,封裝事件,所不同的是,Channel不再是繼承結(jié)構(gòu)中的基類,而是作為一個(gè)實(shí)體;?
這樣,handleEvent方法就不再是一個(gè)純虛函數(shù),而是包含具體的邏輯處理,當(dāng)然,只有最基本的事件判斷,然后調(diào)用上層的讀寫回調(diào):
這樣的關(guān)鍵是設(shè)置一堆回調(diào)函數(shù),通過(guò)boost::function()+boost::bind()可以輕松的做到;
Acceptor 和TcpConnection
Acceptor類,這個(gè)對(duì)應(yīng)到上面的AcceptChannel,但實(shí)現(xiàn)不是通過(guò)繼承,而是通過(guò)組合實(shí)現(xiàn);?
Acceptor用于監(jiān)聽,關(guān)注連接,建立連接后,由TCPConnection來(lái)接管處理;?
這個(gè)類沒(méi)有業(yè)務(wù)處理,用來(lái)處理監(jiān)聽和連接請(qǐng)求到來(lái)后的邏輯;?
所有與事件循環(huán)相關(guān)的都是channel,Acceptor不直接和EventLoop打交道,所以在這個(gè)類中需要有一個(gè)channel的成員,并包含將channel掛到事件循環(huán)中的邏輯(listen());?
TcpConnection,處理連接建立后的收發(fā)數(shù)據(jù);業(yè)務(wù)處理回調(diào)完成;
TCPServer
TCPServer就是膠水,作用有二:
示例對(duì)比
通過(guò)一個(gè)示例來(lái)體會(huì)這兩種實(shí)現(xiàn)中回調(diào)實(shí)現(xiàn)的差別;?
示例:分析讀事件到來(lái)時(shí),底層如何將消息傳遞給用戶邏輯層函數(shù)來(lái)處理的?
OO實(shí)現(xiàn)
channel作為事件的監(jiān)聽接口,加入到事件循環(huán)中,當(dāng)讀事件到來(lái)時(shí),需要調(diào)用?
ConnetionChannel上的handleEvent();而異步數(shù)據(jù)的讀請(qǐng)求最終需要業(yè)務(wù)邏輯層來(lái)判斷是否讀到相應(yīng)的數(shù)據(jù),這就需要從ConnetionChannel中調(diào)用用戶邏輯層上的OnMessage();?
看看這段邏輯的OO實(shí)現(xiàn)序列圖:
?
代碼層面的實(shí)現(xiàn):?
定義用戶邏輯處理類UserLogicCallBack,接收消息的處理函數(shù)為onMessage();?
我們關(guān)注最終底層是如何調(diào)用到業(yè)務(wù)邏輯層的onMessage()的;
callback_用戶邏輯層的對(duì)象在EventLoop初始化時(shí)傳入:
class EventLoop{EventLoop(CallBack & callback):callback_(callback){}CallBack* getCallBack(){return &callback_;}CallBack& callback_; //回調(diào)方法基類 }當(dāng)讀事件到來(lái),在ConnectionChannel中通過(guò)eventloop對(duì)象作為橋梁,回調(diào)消息業(yè)務(wù)處理onMesssage();
void ConnectionChannel::handleRead(){int savedErrno = 0;//返回緩存區(qū)可讀的位置,返回所有讀到的字節(jié),具體到是否收全,//是否達(dá)到業(yè)務(wù)需要的數(shù)據(jù)字節(jié)數(shù),由業(yè)務(wù)層來(lái)判斷處理ssize_t n = inputBuffer_.readFd(fd_, &savedErrno);if (n > 0){ //通過(guò)eventloop作為中介,調(diào)用業(yè)務(wù)層的回調(diào)邏輯loop_->getCallBack()->onMesssage(this,&inputBuffer_);}else if (n == 0){handleClose();}else{errno = savedErrno;handleError();} }函數(shù)式編程實(shí)現(xiàn)
而muduo的回調(diào),使用boost::function()+boost::bind()實(shí)現(xiàn),通過(guò)這兩個(gè)神器,將使用者和實(shí)現(xiàn)者解耦;?
通過(guò)TcpServer,將用戶邏輯層的函數(shù)傳遞到底層;讀事件到來(lái),回調(diào)用戶邏輯;
以下是時(shí)序
?
代碼層面,我們看看用戶邏輯層的代碼是如何傳入的:?
UserLogicCallBack中包含TcpServer的對(duì)象;
在構(gòu)造函數(shù)中,將onMessage傳遞給TcpServer,這是第一次傳遞:
UserLogicCallBack::UserLogicCallBack(muduo::net::EventLoop* loop,const muduo::net::InetAddress& listenAddr): server_(loop, listenAddr, "UserLogicCallBack") {server_.setConnectionCallback(boost::bind(&UserLogicCallBack::onConnection, this, _1));//這里將onMessage傳遞給TcpServerserver_.setMessageCallback(boost::bind(&UserLogicCallBack::onMessage, this, _1, _2, _3)); }TcpServer中的相關(guān)細(xì)節(jié):
class TcpServer{void setMessageCallback(const MessageCallback& cb){ messageCallback_ = cb; }typedef boost::function<void (const TcpConnectionPtr&,Buffer*,Timestamp)> MessageCallback;MessageCallback messageCallback_; };TcpServer新建連接時(shí),將用戶層的回調(diào)函數(shù)繼續(xù)往底層傳遞,這是第二次傳遞:
void TcpServer::newConnection(int sockfd, const InetAddress& peerAddr) {TcpConnectionPtr conn(new TcpConnection(ioLoop,connName,sockfd,localAddr,peerAddr));conn->setConnectionCallback(connectionCallback_);// 這里將onMessage()傳遞給TcpConnectionconn->setMessageCallback(messageCallback_); conn->setWriteCompleteCallback(writeCompleteCallback_);conn->setCloseCallback(boost::bind(&TcpServer::removeConnection, this, _1)); ioLoop->runInLoop(boost::bind(&TcpConnection::connectEstablished, conn)); }通過(guò)這兩次傳遞,messageCallback_作為成員變量保存在TcpConnection中;?
當(dāng)讀事件到來(lái)時(shí),TcpConnection中就可以直接調(diào)用業(yè)務(wù)層的回調(diào)邏輯:
完整時(shí)序詳見最后一節(jié);源代碼來(lái)自muduo庫(kù);
兩者的時(shí)序圖對(duì)比
Reactor的面向?qū)ο缶幊虝r(shí)序:
?
?
Reacotr的函數(shù)式編程時(shí)序:
?
結(jié)論
在面向?qū)ο蟮脑O(shè)計(jì)中,事件底層回調(diào)上層邏輯,本來(lái)和loop這個(gè)發(fā)動(dòng)機(jī)沒(méi)有任何關(guān)系的一件事,卻需要使用它來(lái)作為中轉(zhuǎn);EventLoop作為回調(diào)的中間橋梁,實(shí)在是迫不得已的實(shí)現(xiàn);?
而muduo的設(shè)計(jì)中加入了TcpServer這一膠水層,整個(gè)架構(gòu)就清晰多了;?
boost::function()+boost::bind()讓我們?cè)诨卣{(diào)的實(shí)現(xiàn)上有了更大的自由度,不用再依賴于基于虛函數(shù)的多態(tài)繼承結(jié)構(gòu);但更大的自由度,也更容易帶來(lái)糟糕的設(shè)計(jì),使用boost::function()+boost::bind()基于對(duì)象的設(shè)計(jì),還需要多多體會(huì),多加應(yīng)用;
總結(jié)
以上是生活随笔為你收集整理的Reactor事件驱动的两种设计实现:面向对象 VS 函数式编程的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: Docker 使用Dockerfile构
- 下一篇: ros(2) 发布者publisher的