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 ...
随机推荐
- CircleProgressBar
http://www.eoeandroid.com/thread-333984-1-1.html CircleProgressBar.rar
- javascript 递归之 快速排序
1. 快速排序思想 (1)在数据集之中,选择一个元素作为"基准"(pivot). (2)所有小于"基准"的元素,都移到"基准"的左边:所有大 ...
- careercup-C和C++ 13.2
13.2 浅析哈希表和STL map.对比哈希表和STL map.哈希表是怎么实现的?如果输入数据规模不大, 我们可以使用什么数据结构来代替哈希表. 解答 对比哈希表和STL map 在哈希表中,实值 ...
- Supervised Learning-Regression
假设我们有一张房子属性及其价格之间的关系表(如下图所示) ,根据这些数据如何估计其他房子的价格?我们的第一个反应肯定是参考属性相似的房子的价格.在属性较少时这个方法还行得通,属性太复杂时就不那么简单了 ...
- java基础学习总结二(标识符、字符集、数据类型以及类型转换)
一:标识符 1:标识符可以由字母.数字.下划线_.$符等组成2:标识符的首字母只能是字母.数字.下划线3:标识符不能使用关键字或者保留字4:标识符可以是中文,但是不建议使用中文5:标识符可以任意长,没 ...
- APUE(3)——文件I/O
大多数情况下,我们都会利用Standard I/O Library来进行I/O操作,而这一章所讲的I/O是UNIX系统直接提供的I/O操作,且大多是Unbuffered I/O,即每一次读或写都会出现 ...
- 微信公众平台接口API
<?php /** * Author: helen * CreateTime: 2015/12/9 20:14 * description: 微信公众平台接口API */ class Wecha ...
- Javascript中new
// 加不加new结果都一样 var obj = new Function('var temp = 100;this.temp = 200;return temp + this.temp;'); al ...
- [转]SQL Server 和Oracle 数据类型对应
本文转自:http://blog.sina.com.cn/s/blog_681cd80d0100q84t.html SqlServer 2k转换为Oracle 10g 列名 SqlServer数据类型 ...
- 什么是CS和BS结构,两种结构的区别
什么是CS和BS结构,两种结构的区别 什么是C/S和B/S结构? C/S又称Client/Server或客户/服务器模式.服务器通常采用高性能的PC.工作站或小型机,并采用大型数据库系 ...