说明:本文参照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的关键结构的更多相关文章

  1. muduo学习笔记(二)Reactor关键结构

    目录 muduo学习笔记(二)Reactor关键结构 Reactor简述 什么是Reactor Reactor模型的优缺点 poll简述 poll使用样例 muduo Reactor关键结构 Chan ...

  2. muduo源代码分析--Reactor模式在muduo中的使用

    一. Reactor模式简单介绍 Reactor释义"反应堆",是一种事件驱动机制.和普通函数调用的不同之处在于:应用程序不是主动的调用某个API完毕处理.而是恰恰相反.React ...

  3. ARM-Linux S5PV210 UART驱动(3)----串口核心层、关键结构体、接口关系

    尽管一个特定的UART设备驱动完全可以按照tty驱动的设计方法来设计,即定义tty_driver并实现tty_operations其中的成员函数,但是Linux已经在文件serial_core.c中实 ...

  4. muduo源码分析:组成结构

    muduo整体由许多类组成: 这些类之间有一些依赖关系,如下:

  5. Framebuffer 驱动学习总结(一) ---- 总体架构及关键结构体

    一.Framebuffer 设备驱动总体架构 帧缓冲设备为标准的字符型设备,在Linux中主设备号29,定义在/include/linux/major.h中的FB_MAJOR,次设备号定义帧缓冲的个数 ...

  6. muduo源代码分析--Reactor在模型muduo使用(两)

    一. TcpServer分类: 管理所有的TCP客户连接,TcpServer对于用户直接使用,直接控制由用户生活. 用户只需要设置相应的回调函数(消息处理messageCallback)然后TcpSe ...

  7. Linux多线程服务端编程:使用muduo C++网络库

    内容推荐本 书主要讲述采用现代C++在x86-64 Linux上编写多线程TCP网络服务程序的主流常规技术,重点讲解一种适应性较强的多线程服务器的编程模型,即one loop per thread.这 ...

  8. Mudo C++网络库第八章学习笔记

    muduo网络库的设计与实现 muduo是基于Reactor模式的C++网络库; Reactor的关键结构 Reactor最核心的是事件分发机制, 即将IO multiplexing拿到IO事件分发给 ...

  9. Linux多线程服务器端编程

    目录 Linux多线程服务器端编程 线程安全的对象生命期管理 对象的销毁线程比较难 线程同步精要 借shared_ptr实现写时拷贝(copy-on-write) 多线程服务器的适用场合与常用编程模型 ...

随机推荐

  1. 转: angular编码风格指南

    After reading Google's AngularJS guidelines, I felt they were a little too incomplete and also guide ...

  2. BBM(Bad Block Management)坏块管理

    不管WL算法如何高明,在使用中都会碰到一个头痛的问题,那就是坏块,所以一个SSD必须要有坏块管理机制.何谓坏块?一个闪存块里包含有不稳定的地址,不能保证读/写/擦时数据的准确性.            ...

  3. Android开发笔记--hello world 和目录结构

    原文:Android开发笔记--hello world 和目录结构 每接触一个新东西 都有一个hello world的例子. 1.新建项目 2.配置AVD AVD 没有要新建个,如果不能创建 运行SD ...

  4. Sunday字符串匹配算法

    逛ACM神犇的博客的时候看到的这个神奇的算法 KMP吧,失配函数难理解,代码量长 BF吧,慢,很慢,特别慢. BM吧,我不会写... 现在看到了Sunday算法呀,眼前一亮,神清气爽啊. 字符串匹配算 ...

  5. 动态链接库 DLL

    动态链接库DLL 不使用时不会有任何作用,只有在其他模块调用动态链接库中的函数时,它才发挥作用. 一.静态库与动态库 1.静态库 函数和数据被编译进一个二进制文件(.LIB),编译时,会将其组合起来创 ...

  6. UVA - 10129Play on Words(欧拉路)

    UVA - 10129Play on Words Some of the secret doors contain a very interesting word puzzle. The team o ...

  7. 很郁闷,七日筑基C#第二天的内容未保存

    很郁闷,七日筑基C#第二天的内容写了好几百字未保存,刚才死机了,一下打击得不行了.

  8. 关于js封装框架类库之DOM操作模块(一)

    在前端开发的过程中,javascript极为重要的一个功能就是对DOM对象的操作,而对其封装就是为了更好地进行DOM操作,提高浏览器的支持效率 现在给出一个案例:页面创建三个div,然后给其添加样式 ...

  9. spring MVC中文乱码相关总结

    总结几种方式,都使用的话能解决大多数乱码的情况 1.所有页面使用 <%@page language="java" pageEncoding="UTF-8" ...

  10. BZOJ 1316: 树上的询问( 点分治 + 平衡树 )

    直接点分治, 用平衡树(set就行了...)维护. -------------------------------------------------------------------------- ...