这部分的内容主要包括Epoll/select的封装,在封装好相应函数后,再使用一个类来管理相应事件,实现的文件为pollmgr.{h, cc}。

事件函数封装

可看到pollmgr.h文件下定一个了一个虚基类aio_mgr

 class aio_mgr {
public:
virtual void watch_fd(int fd, poll_flag flag) = ;
virtual bool unwatch_fd(int fd, poll_flag flag) = ;
virtual bool is_watched(int fd, poll_flag flag) = ;
virtual void wait_ready(std::vector<int> *readable, std::vector<int> *writable) = ;
virtual ~aio_mgr() {}
};

这便是具体事件类实现的基类,可看到文件末尾处的继承关系

 class SelectAIO : public aio_mgr {
public : SelectAIO();
~SelectAIO();
void watch_fd(int fd, poll_flag flag);
bool unwatch_fd(int fd, poll_flag flag);
bool is_watched(int fd, poll_flag flag);
void wait_ready(std::vector<int> *readable, std::vector<int> *writable); private: fd_set rfds_;
fd_set wfds_;
int highfds_;
int pipefd_[]; pthread_mutex_t m_; }; #ifdef __linux__
class EPollAIO : public aio_mgr {
public:
EPollAIO();
~EPollAIO();
void watch_fd(int fd, poll_flag flag);
bool unwatch_fd(int fd, poll_flag flag);
bool is_watched(int fd, poll_flag flag);
void wait_ready(std::vector<int> *readable, std::vector<int> *writable); private:
int pollfd_;
struct epoll_event ready_[MAX_POLL_FDS];
int fdstatus_[MAX_POLL_FDS]; };
#endif /* __linux */

相应是使用select和epoll分别实现的事件管理类,其中最主要的方法是wait_ready,这个方法实现了具体的事件查询,其余几个函数用于管理套接字,如增加套接字,删除套接字以及判断套接字是否还存活着。这里我们主要看下epoll实现部分,select实现部分类似。epoll的详解可看这里

 EPollAIO::EPollAIO()
{
pollfd_ = epoll_create(MAX_POLL_FDS);
VERIFY(pollfd_ >= );
bzero(fdstatus_, sizeof(int)*MAX_POLL_FDS);
} EPollAIO::~EPollAIO()
{
close(pollfd_);
} //状态转换
static inline
int poll_flag_to_event(poll_flag flag)
{
int f;
if (flag == CB_RDONLY) {
f = EPOLLIN;
}else if (flag == CB_WRONLY) {
f = EPOLLOUT;
}else { //flag == CB_RDWR
f = EPOLLIN | EPOLLOUT;
}
return f;
}
/*
* 这个函数就相当于:准备下一个监听事件的类型
*/
void
EPollAIO::watch_fd(int fd, poll_flag flag)
{
VERIFY(fd < MAX_POLL_FDS); struct epoll_event ev;
int op = fdstatus_[fd]? EPOLL_CTL_MOD : EPOLL_CTL_ADD;
fdstatus_[fd] |= (int)flag; //边缘触发模式
ev.events = EPOLLET;
ev.data.fd = fd;
//注册读事件
if (fdstatus_[fd] & CB_RDONLY) {
ev.events |= EPOLLIN;
}//注册写事件
if (fdstatus_[fd] & CB_WRONLY) {
ev.events |= EPOLLOUT;
} if (flag == CB_RDWR) {
VERIFY(ev.events == (uint32_t)(EPOLLET | EPOLLIN | EPOLLOUT));
}
//更改
VERIFY(epoll_ctl(pollfd_, op, fd, &ev) == );
} bool
EPollAIO::unwatch_fd(int fd, poll_flag flag)
{
VERIFY(fd < MAX_POLL_FDS);
fdstatus_[fd] &= ~(int)flag; struct epoll_event ev;
int op = fdstatus_[fd]? EPOLL_CTL_MOD : EPOLL_CTL_DEL; ev.events = EPOLLET;
ev.data.fd = fd; if (fdstatus_[fd] & CB_RDONLY) {
ev.events |= EPOLLIN;
}
if (fdstatus_[fd] & CB_WRONLY) {
ev.events |= EPOLLOUT;
} if (flag == CB_RDWR) {
VERIFY(op == EPOLL_CTL_DEL);
}
VERIFY(epoll_ctl(pollfd_, op, fd, &ev) == );
return (op == EPOLL_CTL_DEL);
} bool
EPollAIO::is_watched(int fd, poll_flag flag)
{
VERIFY(fd < MAX_POLL_FDS);
return ((fdstatus_[fd] & CB_MASK) == flag);
}
/**
* 事件循环,查看有哪些事件已经准备好,准备好的事件则插入相应列表中
*/
void
EPollAIO::wait_ready(std::vector<int> *readable, std::vector<int> *writable)
{
//得到已准备好的事件数目
int nfds = epoll_wait(pollfd_, ready_, MAX_POLL_FDS, -);
//遍历套接字数组,将可读/可写套接字添加到readable/writable数组中,便于后面处理
for (int i = ; i < nfds; i++) {
if (ready_[i].events & EPOLLIN) {
readable->push_back(ready_[i].data.fd);
}
if (ready_[i].events & EPOLLOUT) {
writable->push_back(ready_[i].data.fd);
}
}
}

事件管理

在pollmgr.h中还有个重要的类

class aio_callback {
public:
virtual void read_cb(int fd) = ;
virtual void write_cb(int fd) = ;
virtual ~aio_callback() {}
};

这是一个回调虚基类,里面两个函数可从函数名猜到功能,即从对应的套接字读取/写入数据。该基类在后面底层通信中扮演着重要的角色。

然后我们再看后面的PollMgr类,这便是事件管理类,同时它还使用了单例模式。

 class PollMgr {
public:
PollMgr();
~PollMgr(); static PollMgr *Instance();
static PollMgr *CreateInst();
//在对应的套接字上添加事件
void add_callback(int fd, poll_flag flag, aio_callback *ch);
//删除套接字上的所有事件
void del_callback(int fd, poll_flag flag);
bool has_callback(int fd, poll_flag flag, aio_callback *ch);
//阻塞删除套接字,为何阻塞呢?因为删除时,其它线程正在使用该套接字
void block_remove_fd(int fd);
//主要事件循环方法
void wait_loop(); static PollMgr *instance;
static int useful;
static int useless; private:
pthread_mutex_t m_;
pthread_cond_t changedone_c_;
pthread_t th_; aio_callback *callbacks_[MAX_POLL_FDS]; //事件数组,即数组下标为相应的套接字
aio_mgr *aio_; //具体的事件函数类,可实现为epoll/select
bool pending_change_;
};

其中最主要的函数是wait_loop

接下来我们看具体实现。

 PollMgr *PollMgr::instance = NULL;
static pthread_once_t pollmgr_is_initialized = PTHREAD_ONCE_INIT; void
PollMgrInit()
{
PollMgr::instance = new PollMgr();
} PollMgr *
PollMgr::Instance()
{
//保证PollMgrInit在本线程内只初始化一次
pthread_once(&pollmgr_is_initialized, PollMgrInit);
return instance;
}

这里实现单例,pthread_once保证了线程中只初始化一次PollMgrInit()函数,所以在具体使用时,只需调用PollMgr::Instance()即可获得该管理类,再在其上处理各种各种事件。这里有个小疑问是:instance变量不应该是私有变量吗?

接下来我们看构造析构函数:

PollMgr::PollMgr() : pending_change_(false)
{
bzero(callbacks_, MAX_POLL_FDS*sizeof(void *));
//aio_ = new SelectAIO();
aio_ = new EPollAIO();
VERIFY(pthread_mutex_init(&m_, NULL) == );
VERIFY(pthread_cond_init(&changedone_c_, NULL) == );
//this表示本类,wait_loop是本类中的一个方法,false表示不分离(detach)
VERIFY((th_ = method_thread(this, false, &PollMgr::wait_loop)) != );
} PollMgr::~PollMgr()
{
//never kill me!!!
VERIFY();
}

构造函数中初始化了事件类,使用了EpollAIO类,初始化了互斥量和条件变量,然后创建了一个线程调用wait_loop。有意思的是析构函数(never kill me)

接下来是几个管理函数,管理套接字和回调的函数

 void
PollMgr::add_callback(int fd, poll_flag flag, aio_callback *ch)
{
VERIFY(fd < MAX_POLL_FDS); ScopedLock ml(&m_);
aio_->watch_fd(fd, flag); VERIFY(!callbacks_[fd] || callbacks_[fd]==ch);
callbacks_[fd] = ch;
} //remove all callbacks related to fd
//the return guarantees that callbacks related to fd
//will never be called again
void
PollMgr::block_remove_fd(int fd)
{
ScopedLock ml(&m_);
aio_->unwatch_fd(fd, CB_RDWR);
pending_change_ = true;
VERIFY(pthread_cond_wait(&changedone_c_, &m_)==);
callbacks_[fd] = NULL;
} //删除相应的回调函数
void
PollMgr::del_callback(int fd, poll_flag flag)
{
ScopedLock ml(&m_);
if (aio_->unwatch_fd(fd, flag)) {
callbacks_[fd] = NULL;
}
} //
bool
PollMgr::has_callback(int fd, poll_flag flag, aio_callback *c)
{
ScopedLock ml(&m_);
if (!callbacks_[fd] || callbacks_[fd]!=c)
return false; return aio_->is_watched(fd, flag);
}

下面便是循环的主方法,该方法一直循环获取相应的事件,但此方法有个问题是,当某个回调读取需要长时间阻塞时,

会耽误后续事件的读取或写入。

//循环的主方法
void
PollMgr::wait_loop()
{ std::vector<int> readable; //可读套接字的vector
std::vector<int> writable; //可写套接字的vector
//
while () {
{
ScopedLock ml(&m_);
if (pending_change_) {
pending_change_ = false;
VERIFY(pthread_cond_broadcast(&changedone_c_)==);
}
}
//首先清空两个vector
readable.clear();
writable.clear();
//这里便监听了事件,读或写事件,有时间发生便将事件的fd插入相应的vector
aio_->wait_ready(&readable,&writable);
//如果这次没有可读和可写事件,则继续下一次循环
if (!readable.size() && !writable.size()) {
continue;
}
//no locking of m_
//because no add_callback() and del_callback should
//modify callbacks_[fd] while the fd is not dead
for (unsigned int i = ; i < readable.size(); i++) {
int fd = readable[i];
if (callbacks_[fd]) //相应的回调函数读取套接字上的数据
callbacks_[fd]->read_cb(fd);
} for (unsigned int i = ; i < writable.size(); i++) {
int fd = writable[i];
if (callbacks_[fd])
callbacks_[fd]->write_cb(fd);
}
}
}

具体使用时,只需获得单例类即可,然后再添加相应的套接字及回调函数,添加都是线程安全的,因为在相应的实现上都会阻塞在内部互斥变量m_上

MIT 2012分布式课程基础源码解析-事件管理封装的更多相关文章

  1. MIT 2012分布式课程基础源码解析一-源码概述

    课程主页 课程介绍:本课程会在给出的源码的基础上要求完成8个lab Lab overviewLab 1 - Lock ServerLab 2 - Basic File ServerLab 3 - MK ...

  2. MIT 2012分布式课程基础源码解析-线程池实现

    主要内容 ScopedLock 队列实现 线程池实现 在正式讲解线程池实现之前,先讲解两个有用的工具类: ScopedLock fifo队列 ScopedLock: ScopedLock是局域锁的实现 ...

  3. MIT 2012 分布式课程基础源码解析-底层通讯实现

    本节内容和前节事件管理封装是息息相关的,本节内容主要包含的代码在connection{.h, .cc}中. 这里面最主要的有两个类:connection类和tcpsconn类,connetion类主要 ...

  4. FastDFS分布式文件系统及源码解析

    记录一次本人学习FastDFS-分布式文件系统的学习过程,希望能帮助到有需要的人. 首选得对此技术有个大概的了解,可以参考 https://www.cnblogs.com/centos2017/p/7 ...

  5. Netty源码解析 -- 事件循环机制实现原理

    本文主要分享Netty中事件循环机制的实现. 源码分析基于Netty 4.1 EventLoop 前面分享服务端和客户端启动过程的文章中说过,Netty通过事件循环机制(EventLoop)处理IO事 ...

  6. Ejabberd源码解析前奏--管理

    一.ejabberdctl 使用ejabberdctl命令行管理脚本,你可以执行ejabberdctl命令和一些普通的ejabberd命令(后面会详细解说).这意味着你可以在一个本地或远程ejabbe ...

  7. Spring源码解析-事件

    Spring事件的组件 主要是3个组件: 1.ApplicationEvent   事件 2.ApplicationListener 监听器,对事件进行监听 3.ApplicationEventMul ...

  8. [源码解析] 模型并行分布式训练Megatron (5) --Pipedream Flush

    [源码解析] 模型并行分布式训练Megatron (5) --Pipedream Flush 目录 [源码解析] 模型并行分布式训练Megatron (5) --Pipedream Flush 0x0 ...

  9. [源码解析] PyTorch 分布式 Autograd (4) ---- 如何切入引擎

    [源码解析] PyTorch 分布式 Autograd (4) ---- 如何切入引擎 目录 [源码解析] PyTorch 分布式 Autograd (4) ---- 如何切入引擎 0x00 摘要 0 ...

随机推荐

  1. perl dtrace2

    http://search.cpan.org/~chrisa/Devel-DTrace-Provider-1.11/lib/Devel/DTrace/Provider.pm

  2. 学习笔记之#pragma

    http://baike.baidu.com/link?url=lxA9Wl1KnacWUDZYz5U06iKMkUAeI6dr0x1wQ4i-rqf6Dpk6hEeOOaj2XhvGx9VLVm2z ...

  3. Android中的距离单位

    px 像素:每个px对应屏幕上面的一个点 dip或dp(device independent pixels 设备独立像素):一种基于屏幕密度的抽象单位.在每英寸160点的显示器上,1dip=1px.但 ...

  4. Apache的rewrite规则详细介绍

    Apache的rewrite规则详细介绍 发布日期:2008-09-02 16:16 来源: 作者: 点击:7044 rewrite标志 R[=code](force redirect) 强制外部重定 ...

  5. 如何用 PHPMailer 来发送邮件?

    <?php require_once('mantisbt-1.2.15/library/phpmailer/class.phpmailer.php'); $mail= new PHPMailer ...

  6. Listview实现分页下载

    分析: * 1.初始控件,展示第一页数据 * 2.设置触发条件 * 1.设置滚动监听 * 2.判断是否在底部 * 3.判断是否停止滚动 * 4.更改网络请求参数为下一页 * 5.异步网络请求 * 6. ...

  7. 各I/O模型 对应Web服务应用模型(select,poll,epoll,kevent,"/dev/poll")

    一.利用select多路复用I/O的Web服务应用模型  /* 可读.可写.异常三种文件描述符集的申明和初始化.*/ fd_set readfds, writefds, exceptionfds; F ...

  8. APUE(3)——文件I/O

    大多数情况下,我们都会利用Standard I/O Library来进行I/O操作,而这一章所讲的I/O是UNIX系统直接提供的I/O操作,且大多是Unbuffered I/O,即每一次读或写都会出现 ...

  9. 优雅的使用 PhpStorm 来开发 Laravel 项目

    [目录] Prerequisites plugin installation and configuration 1 Ensure Composer is initialized 2 Install ...

  10. 最新版spark1.1.0集群安装配置

    和分布式文件系统和NoSQL数据库相比而言,spark集群的安装配置还算是比较简单的: 很多教程提到要安装java和scala,但我发现spark最新版本是包含scala的,JRE采用linux内嵌的 ...