muduo简化(1):Reactor的关键结构
说明:本文参照muduo代码,主要用意是简化muduo代码呈现其主要结构,并脱离muduo的文件依赖。
本节简化的是Reactor的关键结构部分:EventLoop、Poller、Channel。遵照one loop per thread原则,一个事件循环对应一个IO线程,IO线程运行EventLoop事件主循环,该主循环loop调用IO复用器Poller监听事件集合,并将就绪事件通过事件分发器Channel执行相应的事件回调。通过C++封装的代码如下,名为Reactor.hpp(需将测试代码注释掉):
#include<iostream>
#include<stdio.h>
#include<vector>
#include<map>
#include<poll.h>
#include<boost/noncopyable.hpp>
#include<boost/function.hpp>
#include<assert.h>
#include<boost/scoped_ptr.hpp>
#include<stdlib.h>
#include<sys/syscall.h>
#include<pthread.h>
#include<unistd.h>
#include<string.h>
using namespace std;
using namespace boost;
//以下类都是在同一个IO线程中运行为线程安全的故不需要同步机制 class Channel;//前向声明,事件分发器主要用于事件注册与事件处理(事件回调)
class Poller;//IO复用机制,主要功能是监听事件集合,即select,poll,epoll的功能 //事件循环,一个线程一个事件循环即one loop per thread,其主要功能是运行事件循环如等待事件发生然后处理发生的事件
class EventLoop:noncopyable{
public:
EventLoop();
~EventLoop();
void loop();//事件循环主体
void quit();//终止事件循环,通过设定标志位所以有一定延迟
void updateChannel(Channel* channel);//更新事件分发器Channel,完成文件描述符fd向事件集合注册事件及事件回调函数
void assertInLoopThread(){//若运行线程不拥有EventLoop则退出,保证one loop per thread
if(!isInLoopThread()){
abortNotInLoopThread();
}
}
bool isInLoopThread() const{return threadID_==syscall(SYS_gettid);}//判断运行线程是否为拥有此EventLoop的线程
private:
void abortNotInLoopThread();//在不拥有EventLoop线程中终止
typedef vector<Channel*> ChannelList;//事件分发器Channel容器,一个Channel只负责一个文件描述符fd的事件分发
bool looping_;//事件循环主体loop是运行标志
bool quit_;//取消循环主体标志
const pid_t threadID_;//EventLoop的附属线程ID
scoped_ptr<Poller> poller_;//IO复用器Poller用于监听事件集合
ChannelList activeChannels_;//类似与poll的就绪事件集合,这里集合换成Channel(事件分发器具备就绪事件回调功能)
}; //IO Multiplexing Poller即poll的封装,主要完成事件集合的监听
class Poller:noncopyable{
public:
typedef vector<Channel*> ChannelList;//Channel容器(Channel包含了文件描述符fd和fd注册的事件及事件回调函数),Channel包含文件描述符及其注册事件及其事件回调函数,这里主要用于返回就绪事件集合
Poller(EventLoop* loop);
~Poller();
void Poll(int timeoutMs,ChannelList* activeChannels);//监听事件集合,通过activeChannels返回就绪事件集合
void updateChannel(Channel* channel);//向事件集合中添加描述符欲监听的事件(channel中含有fd及其事件)
void assertInLoopThread(){//判定是否和EventLoop的隶属关系,EventLoop要拥有此Poller
ownerLoop_->assertInLoopThread();
}
private:
void fillActiveChannels(int numEvents,ChannelList* activeChannels) const;//将就绪的事件添加到activeChannels中用于返回就绪事件集合
typedef vector<struct pollfd> PollFdList;//struct pollfd是poll系统调用监听的事件集合参数
typedef map<int,Channel*> ChannelMap;//文件描述符fd到IO分发器Channel的映射,通过fd可以快速找到Channel
//注意:Channel中有fd成员可以完成Channel映射到fd的功能,所以fd和Channel可以完成双射
EventLoop* ownerLoop_;//隶属的EventLoop
PollFdList pollfds_;//监听事件集合
ChannelMap channels_;//文件描述符fd到Channel的映射
}; //事件分发器该类包含:文件描述符fd、fd欲监听的事件、事件的处理函数(事件回调函数)
class Channel:noncopyable{
public:
typedef function<void()> EventCallback;//事件回调函数类型,回调函数的参数为空,这里将参数类型已经写死了
Channel(EventLoop* loop,int fd);//一个Channel只负责一个文件描述符fd但Channel不拥有fd,可见结构应该是这样的:EventLoop调用Poller监听事件集合,就绪的事件集合元素就是Channel。但Channel的功能不仅是返回就绪事件,还具备事件处理功能
void handleEvent();//处理事件回调
void setReadCallBack(const EventCallback& cb){//可读事件回调
readCallback=cb;
}
void setWriteCallback(const EventCallback& cb){//可写事件回调
writeCallback=cb;
}
void setErrorCallback(const EventCallback& cb){//出错事件回调
errorCallback=cb;
}
int fd() const{return fd_;}//返回Channel负责的文件描述符fd,即建立Channel到fd的映射
int events() const{return events_;}//返回fd域注册的事件类型
void set_revents(int revt){//设定fd的就绪事件类型,再poll返回就绪事件后将就绪事件类型传给此函数,然后此函数传给handleEvent,handleEvent根据就绪事件的类型决定执行哪个事件回调函数
revents_=revt;
}
bool isNoneEvent() const{//fd没有想要注册的事件
return events_==kNoneEvent;
}
void enableReading(){//fd注册可读事件
events_|=kReadEvent;
update();
}
void enableWriting(){//fd注册可写事件
events_|=kWriteEvent;
update();
}
int index(){return index_;}//index_是本Channel负责的fd在poll监听事件集合的下标,用于快速索引到fd的pollfd
void set_index(int idx){index_=idx;}
EventLoop* ownerLoop(){return loop_;}
private:
void update();
static const int kNoneEvent;//无任何事件
static const int kReadEvent;//可读事件
static const int kWriteEvent;//可写事件 EventLoop* loop_;//Channel隶属的EventLoop(原则上EventLoop,Poller,Channel都是一个IO线程)
const int fd_;//每个Channel唯一负责的文件描述符,Channel不拥有fd
int events_;//fd_注册的事件
int revents_;//通过poll返回的就绪事件类型
int index_;//在poll的监听事件集合pollfd的下标,用于快速索引到fd的pollfd
EventCallback readCallback;//可读事件回调函数,当poll返回fd_的可读事件时调用此函数执行相应的事件处理,该函数由用户指定
EventCallback writeCallback;//可写事件回调函数
EventCallback errorCallback;//出错事件回调函数
}; /*
*EventLoop成员实现
*/
__thread EventLoop* t_loopInThisThread=0;//线程私有数据表示线程是否拥有EventLoop
const int kPollTimeMs=10000;//poll等待时间
EventLoop::EventLoop():looping_(false),quit_(false),threadID_(syscall(SYS_gettid)),poller_(new Poller(this)){
if(!t_loopInThisThread){
t_loopInThisThread=this;//EventLoop构造时线程私有数据记录
}
}
EventLoop::~EventLoop(){
assert(!looping_);
t_loopInThisThread=NULL;//EventLoop析构将其置空
}
void EventLoop::loop(){//EventLoop主循环,主要功能是监听事件集合,执行就绪事件的处理函数
assert(!looping_);
assertInLoopThread();
looping_=true;
quit_=false;
while(!quit_){
activeChannels_.clear();
poller_->Poll(kPollTimeMs,&activeChannels_);//activeChannels是就绪事件
for(ChannelList::iterator it=activeChannels_.begin();it!=activeChannels_.end();it++){
(*it)->handleEvent();//处理就绪事件的回调函数
}
}
looping_=false;
}
void EventLoop::quit(){
quit_=true;//停止主循环标志,主循环不会马上停止有延迟
}
void EventLoop::updateChannel(Channel* channel){//主要用于文件描述符添加到poll的监听事件集合中
assert(channel->ownerLoop()==this);
assertInLoopThread();
poller_->updateChannel(channel);
}
void EventLoop::abortNotInLoopThread(){
printf("abort not in Loop Thread\n");
abort();//非本线程调用强行终止
}
/*
*Poller成员实现
*/
Poller::Poller(EventLoop* loop):ownerLoop_(loop){}//Poller明确所属的EventLoop
Poller::~Poller(){}
void Poller::Poll(int timeoutMs,ChannelList* activeChannels){
int numEvents=poll(&*pollfds_.begin(),pollfds_.size(),timeoutMs);//poll监听事件集合pollfds_
if(numEvents>0){
fillActiveChannels(numEvents,activeChannels);//将就绪的事件添加到activeChannels
}
else if(numEvents==0){
}
else{
printf("Poller::Poll error\n");
}
}
void Poller::fillActiveChannels(int numEvents,ChannelList* activeChannels) const{//将就绪事件通过activeChannels返回
for(PollFdList::const_iterator pfd=pollfds_.begin();pfd!=pollfds_.end()&&numEvents>0;++pfd){
if(pfd->revents>0){
--numEvents;//若numEvents个事件全部找到就不需要再遍历容器剩下的部分
ChannelMap::const_iterator ch=channels_.find(pfd->fd);
assert(ch!=channels_.end());
Channel* channel=ch->second;
assert(channel->fd()==pfd->fd);
channel->set_revents(pfd->revents);
activeChannels->push_back(channel);
}
}
}
void Poller::updateChannel(Channel* channel){
assertInLoopThread();
if(channel->index()<0){//若channel的文件描述符fd没有添加到poll的监听事件集合中
assert(channels_.find(channel->fd())==channels_.end());
struct pollfd pfd;
pfd.fd=channel->fd();
pfd.events=static_cast<short>(channel->events());
pfd.revents=0;
pollfds_.push_back(pfd);
int idx=static_cast<int>(pollfds_.size())-1;
channel->set_index(idx);
channels_[pfd.fd]=channel;
}
else{//若已经添加到监听事件集合中,但是需要修改
assert(channels_.find(channel->fd())!=channels_.end());
assert(channels_[channel->fd()]==channel);
int idx=channel->index();
assert(0<=idx&&idx<static_cast<int>(pollfds_.size()));
struct pollfd& pfd=pollfds_[idx];
assert(pfd.fd==channel->fd()||pfd.fd==-1);
pfd.events=static_cast<short>(channel->events());//修改注册事件类型
pfd.revents=0;
if(channel->isNoneEvent()){
pfd.fd=-1;//若无事件则poll忽略
}
}
}
/*
*Channel成员实现
*/
const int Channel::kNoneEvent=0;//无事件
const int Channel::kReadEvent=POLLIN|POLLPRI;//可读事件
const int Channel::kWriteEvent=POLLOUT;//可写事件
Channel::Channel(EventLoop* loop,int fdArg):loop_(loop),fd_(fdArg),events_(0),revents_(0),index_(-1){}
void Channel::update(){//添加或修改文件描述符的事件类型
loop_->updateChannel(this);
}
void Channel::handleEvent(){//处理就绪事件的处理函数
if(revents_&POLLNVAL){
printf("Channel::handleEvent() POLLNVAL\n");
}
if(revents_&(POLLERR|POLLNVAL)){//出错回调
if(errorCallback)
errorCallback();
}
if(revents_&(POLLIN|POLLPRI|POLLRDHUP)){//可读回调
if(readCallback)
readCallback();
}
if(revents_&POLLOUT){//可写回调
if(writeCallback)
writeCallback();
}
}
/*
*测试代码,主线程往管道写数据,子线程通过EventLoop监听管道读端然后执行相应的可读回调(读取数据并输出)
*/
int pipefd[2];
EventLoop* loop;
void ReadPipe(){
char buf[1024];
read(pipefd[0],buf,1024);
printf("%s\n",buf);
loop->quit();//执行完可读回调后终止EventLoop的事件循环loop
}
void* threadFun(void* arg){
loop=new EventLoop();
Channel channel(loop,pipefd[0]);
channel.setReadCallBack(ReadPipe);
channel.enableReading();
loop->loop();
}
int main(){
pthread_t pid;
pipe(pipefd);
pthread_create(&pid,NULL,&threadFun,NULL);
const char* ptr="Can you get this data?";
write(pipefd[1],ptr,strlen(ptr));
pthread_join(pid,NULL);
return 0;
}
测试代码的输出:
Can you get this data?
muduo简化(1):Reactor的关键结构的更多相关文章
- muduo学习笔记(二)Reactor关键结构
目录 muduo学习笔记(二)Reactor关键结构 Reactor简述 什么是Reactor Reactor模型的优缺点 poll简述 poll使用样例 muduo Reactor关键结构 Chan ...
- muduo源代码分析--Reactor模式在muduo中的使用
一. Reactor模式简单介绍 Reactor释义"反应堆",是一种事件驱动机制.和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完毕处理.而是恰恰相反.React ...
- ARM-Linux S5PV210 UART驱动(3)----串口核心层、关键结构体、接口关系
尽管一个特定的UART设备驱动完全可以按照tty驱动的设计方法来设计,即定义tty_driver并实现tty_operations其中的成员函数,但是Linux已经在文件serial_core.c中实 ...
- muduo源码分析:组成结构
muduo整体由许多类组成: 这些类之间有一些依赖关系,如下:
- Framebuffer 驱动学习总结(一) ---- 总体架构及关键结构体
一.Framebuffer 设备驱动总体架构 帧缓冲设备为标准的字符型设备,在Linux中主设备号29,定义在/include/linux/major.h中的FB_MAJOR,次设备号定义帧缓冲的个数 ...
- muduo源代码分析--Reactor在模型muduo使用(两)
一. TcpServer分类: 管理所有的TCP客户连接,TcpServer对于用户直接使用,直接控制由用户生活. 用户只需要设置相应的回调函数(消息处理messageCallback)然后TcpSe ...
- Linux多线程服务端编程:使用muduo C++网络库
内容推荐本 书主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread.这 ...
- Mudo C++网络库第八章学习笔记
muduo网络库的设计与实现 muduo是基于Reactor模式的C++网络库; Reactor的关键结构 Reactor最核心的是事件分发机制, 即将IO multiplexing拿到IO事件分发给 ...
- Linux多线程服务器端编程
目录 Linux多线程服务器端编程 线程安全的对象生命期管理 对象的销毁线程比较难 线程同步精要 借shared_ptr实现写时拷贝(copy-on-write) 多线程服务器的适用场合与常用编程模型 ...
随机推荐
- SPOJ 687 Repeats(后缀数组+ST表)
[题目链接] http://www.spoj.com/problems/REPEATS/en/ [题目大意] 求重复次数最多的连续重复子串的长度. [题解] 考虑错位匹配,设重复部分长度为l,记s[i ...
- Noip2013心态调整
决定成绩的,很多时候可能不是实力,而是心态,一年走来,承受着一次次失败,怀疑,背负着希望与压力,突然发现,只有拥有过,失去过,才可以真正去超越,我希望完成我的梦想,但是唯有放下梦想,才可以走向它. 心 ...
- swith
“开关”(Switch)有时也被划分为一种“选择语句”.根据一个整数表达式的值,switch语句可从一系列代码选出一段执行.它的格式如下: switch(整数选择因子) { case 整数值1 : 语 ...
- 命令行方式运行yii2程序
测试环境,yii 2.0.3版本 web访问方式,控制器放在controllers目录下 ,浏览器访问如下地址 http://127.0.0.1/index.php?r=[controller-nam ...
- 【HDU】病毒侵袭持续中(AC自己主动机+map)
一開始一直WA,之后发现这道题不止一组输入,改成多组输入之后就过了. 利用map把每一个字符串映射到它相应的结点上即可了. 11909467 2014-10-19 11:54:00 Accepted ...
- c 转置字符串You are a so cheap man ->man cheap so a are You
解题思路: 1.将字符串转置 2.对转置后的字符串中单词转置 #include<stdio.h> #include<string.h> #include<stdlib.h ...
- c#学习心得,慢慢添加,如果有错误希望大家留言,我刚开始学
1.class类:相当于整个项目的一个功能性程序,为了阐述系统中某个对象的功能. 方法:相当于程序的一个功能部件.可以被其他方法或类调用?感觉这个问题有点复杂 c#框架结构:我目前接触到的 using ...
- 上一篇下一篇 排序 (非ID字段排序)
网上看了很多关于"上一篇下篇"的文章,可大都是按ID排序. 实际上,很少有按ID排序的. 分享下我的单独排序字段的写法,主要分为ms sql2000 和 ms 2005及以上版本. ...
- php如何开启GD库
GD库是干什么用的呢!它是php处理图形的扩展库,GD库提供了一系列用来处理图片的API,使用GD库可以处理图片,或者生成图片.GD库在php中默认是没有开启的,如果想让它支持图片处理功能,那么就要手 ...
- python自学笔记(四)python基本数据类型之元组、集合、字典
一.元组tuple 特性 1.有序集合 2.通过偏移来取数据 3.不可变对象,不能在原地修改内存,没有排序.修改等操作 元组不可变的好处:保证数据的安全,比如我们传给一个不熟悉的方法,确保不会改变我们 ...