这部分的内容主要包括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. android有点纠结的小问题

    1.点击一个listview的item,以popupwindow的形式展示一个菜单.popupwindow以动画的形式展现,可一直没有预期的效果 解决方案: popupWindow.setBackgr ...

  2. 【转】使用BBB的device tree和cape(重新整理版)

    只要你想用BBB做哪怕一丁点涉及到硬件的东西,你就不可避免地要用到cape和device tree的知识.所以尽管它们看起来很陌生而且有点复杂,但还是得学.其实用起来不难的.下面我只讲使用时必须会的内 ...

  3. Navigation Bar上的返回按钮文本颜色,箭头颜色以及导航栏按钮的颜色

    自从IOS7后UINavigationBar的一些属性的行为发生了变化.你可以在下图看到: 现在,如果你要修改它们的颜色,用下面的代码: self.navigationController.navig ...

  4. MySQL(8):数值类型详细分析

    1.日期和时间类型 2.varchar和char 固定长度 (char) 或可变长度 (varchar) 字符数据类型.  例如: a char(10)b varchar(10)都存入'abc'a要求 ...

  5. Android之HTTP网络通信--GET传递(二)

    根据上一篇写的是实现了通过url接口将接口中的数据显示出来,这次根据上一篇的基础,进一步说明一下AsynTask的使用. AsynTask类有几个函数是大家必须知道的. doInBackGround( ...

  6. OC中-数组是如何遍历的?

    #import <Foundation/Foundation.h> int main (int argc, const char * argv[]) { NSAutoreleasePool ...

  7. Android 自学之拖动条SeekBar

    拖动条(SeekBar)和进度条非常相似,只是进度条采用颜色填充来表明进度完成的程度,而拖动条则通过滑块的位置来标识数值----而且拖动条允许用户拖动滑动块来改变值,因此拖动条通常用于对系统的某种数值 ...

  8. Linux下librdkafka客户端的编译运行

    Linux下librdkafka客户端的编译运行 librdkafka是一个开源的Kafka客户端C/C++实现,提供了Kafka生产者.消费者接口. 由于项目需要,我要将Kafka生产者接口封装起来 ...

  9. 【排障】Outlook Express 2G收件箱大小限制

    Outlook Express 2G收件箱大小限制 文:铁乐猫 ----------------------------- Outlook Express(以下简称OE)客户端收件箱大于或接近2G时, ...

  10. PHP之网络编程

    GET: $htmlsource=file_get_contents("http://192.168.0.13/s/interface/shangpin/shangpinDL"); ...