MIT 2012分布式课程基础源码解析-事件管理封装
这部分的内容主要包括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分布式课程基础源码解析-事件管理封装的更多相关文章
- MIT 2012分布式课程基础源码解析一-源码概述
课程主页 课程介绍:本课程会在给出的源码的基础上要求完成8个lab Lab overviewLab 1 - Lock ServerLab 2 - Basic File ServerLab 3 - MK ...
- MIT 2012分布式课程基础源码解析-线程池实现
主要内容 ScopedLock 队列实现 线程池实现 在正式讲解线程池实现之前,先讲解两个有用的工具类: ScopedLock fifo队列 ScopedLock: ScopedLock是局域锁的实现 ...
- MIT 2012 分布式课程基础源码解析-底层通讯实现
本节内容和前节事件管理封装是息息相关的,本节内容主要包含的代码在connection{.h, .cc}中. 这里面最主要的有两个类:connection类和tcpsconn类,connetion类主要 ...
- FastDFS分布式文件系统及源码解析
记录一次本人学习FastDFS-分布式文件系统的学习过程,希望能帮助到有需要的人. 首选得对此技术有个大概的了解,可以参考 https://www.cnblogs.com/centos2017/p/7 ...
- Netty源码解析 -- 事件循环机制实现原理
本文主要分享Netty中事件循环机制的实现. 源码分析基于Netty 4.1 EventLoop 前面分享服务端和客户端启动过程的文章中说过,Netty通过事件循环机制(EventLoop)处理IO事 ...
- Ejabberd源码解析前奏--管理
一.ejabberdctl 使用ejabberdctl命令行管理脚本,你可以执行ejabberdctl命令和一些普通的ejabberd命令(后面会详细解说).这意味着你可以在一个本地或远程ejabbe ...
- Spring源码解析-事件
Spring事件的组件 主要是3个组件: 1.ApplicationEvent 事件 2.ApplicationListener 监听器,对事件进行监听 3.ApplicationEventMul ...
- [源码解析] 模型并行分布式训练Megatron (5) --Pipedream Flush
[源码解析] 模型并行分布式训练Megatron (5) --Pipedream Flush 目录 [源码解析] 模型并行分布式训练Megatron (5) --Pipedream Flush 0x0 ...
- [源码解析] PyTorch 分布式 Autograd (4) ---- 如何切入引擎
[源码解析] PyTorch 分布式 Autograd (4) ---- 如何切入引擎 目录 [源码解析] PyTorch 分布式 Autograd (4) ---- 如何切入引擎 0x00 摘要 0 ...
随机推荐
- 使用python编写批量卸载android应用的脚本
该脚本的功能是卸载android手机中安装的所有第三方应用,主要是使用adb shell pm.adb uninstall 命令,所以使用的前提是需要配好adb的环境变量,下面上代码: #!/usr/ ...
- mac中vmware tools进行磁盘压缩和vmware-tools-cli的使用方法
前言: 高高兴兴的在vmware9.0中安装了mac10.8系统,然后学习iphone开发,但是发现下载的pdf都是基于xcode3.2.5的,又在10.8上面安装3.2.5,出现“五国”无法解决,最 ...
- android离线安装adt
打开Eclipse, 在菜单栏上选择help->Install New SoftWare 出现如下界面: 点击 Add按钮,出现如下界面 在Name这而随意输入一个名字:ADT15:点击打开Ar ...
- Response
Response This improved Response API, able to simplify the Framework's Response management. Practical ...
- VB.NET中使用代表对方法异步调用
按照我们常规的思维方式,计算机应该是干完一件事,然后再干下一件.用术语来说,这种执行任务的方式叫做同步执行(Synchronous Execution).既然这样,那么为什么要引入异步执行的概念呢? ...
- UILabel 自动换行 和支持换行符
这个主要是 lable对\n换行符号的支持,有的时候我们从网页或者后台拿到的数据需要处理一下: 这里没什么要说的,注意两点,一个是label的numofline属性的值要为0 或者不能为1 这样la ...
- 配置opencv
先把opencv配置起来: 详细参见: http://blog.163.com/chen_dawn/blog/static/1125063201461695238801/ 我的机器的配置方法: 先去环 ...
- hadoop安装包的目录结构
初次接触Hadoop,了解了Hadoop安装包的目录结构,和大家分享下: bin:Hadoop最基本的管理脚本和使用脚本的目录,这些脚本是sbin目录下管理脚本的基础实现,用户可以直接使用这些脚本管理 ...
- jQuery图片无缝滚动
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/ ...
- win7 X64可用的单文件IE6
由于日常调试需要用到老版本的ie,没有办法!用ietest老师感觉不好使,动不动就死了.就找到了ie的单文件版,有博主 小明爱切糕制作,IE6.7.8单文件版本 http://www.cnblogs. ...