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 ...
随机推荐
- C++ typedef typename
[cpp] view plaincopy template<typename T> class A { public: typedef T a_type; }; template<t ...
- What Influences Method Call Performance in Java?--reference
reference from:https://www.voxxed.com/blog/2015/02/too-fast-too-megamorphic-what-influences-method-c ...
- JVMInternals--reference
This article explains the internal architecture of the Java Virtual Machine (JVM). The following dia ...
- iOS 符号表恢复 & 逆向支付宝
推荐序 本文介绍了恢复符号表的技巧,并且利用该技巧实现了在 Xcode 中对目标程序下符号断点调试,该技巧可以显著地减少逆向分析时间.在文章的最后,作者以支付宝为例,展示出通过在 UIAlertVie ...
- Java基础知识强化之网络编程笔记12:TCP之TCP协议上传文本文件并给出反馈
1. 客户端: package cn.itcast_12; import java.io.BufferedReader; import java.io.BufferedWriter; import j ...
- MySQL(9):数据表的约束(列的属性)
1.首先我们看一下这个图: 1.NULL| not NULL是否为空 规定一个字段的值是否为NULL 2.Default value 字段默认值属性 常见的是一个字段不能为空,而且存在默认值 ...
- 配置LINUX为路由
配置:关闭防火墙 linux1 地址1: 192.168.10.10/24 地址2:192.168.20.10/24(不指定网关,做为路由,自己就是网关) linux2 地址1: 192. ...
- 关于Eclipse插件开发(五)-----编辑器类方法的使用说明
上面有讲ChinaEditor类继承EditorPart抽象类时,只实现了init,createPartControl两个方法,本节将逐步讲解其他的5个方法的用法. EditorPart方法的执行情况 ...
- css3渐变、背景、倒影、变形
一.背景切割background-clip 语法:background-clip:border-box | padding-box | content-box: border-box 超出b ...
- 基于spark实现表的join操作
1. 自连接 假设存在如下文件: [root@bluejoe0 ~]# cat categories.csv 1,生活用品,0 2,数码用品,1 3,手机,2 4,华为Mate7,3 每一行的格式为: ...