从epoll构建muduo-4 加入Channel
2015-12-04 18:10
459 查看
mini-muduo版本传送门
version 0.00 从epoll构建muduo-1 mini-muduo介绍
version 0.01 从epoll构建muduo-2 最简单的epoll
version 0.02 从epoll构建muduo-3 加入第一个类,顺便介绍reactor
version 0.03 从epoll构建muduo-4 加入Channel
version 0.04 从epoll构建muduo-5 加入Acceptor和TcpConnection
version 0.05 从epoll构建muduo-6 加入EventLoop和Epoll
version 0.06 从epoll构建muduo-7 加入IMuduoUser
version 0.07 从epoll构建muduo-8 加入发送缓冲区和接收缓冲区
version 0.08 从epoll构建muduo-9 加入onWriteComplate回调和Buffer
version 0.09 从epoll构建muduo-10 Timer定时器
version 0.11 从epoll构建muduo-11 单线程Reactor网络模型成型
version 0.12 从epoll构建muduo-12 多线程代码入场
version 0.13 从epoll构建muduo-13 Reactor + ThreadPool 成型
mini-muduo v 0.03版本,这是个版本最重要的修改是加入了一个名为Channel的类。完整可运行的示例可从github下载,使用命令git checkout v0.03可切换到此版本,在线浏览此版本到这里
介绍一下Channel类,先看其声明,这里特别要注意_events和_revents,前者是要关注的事件,后者是发生的事件,不仔细看容易混淆。名字的来源是poll(2)的struct pollfd
[cpp] view
plaincopyprint?
7 class Channel
8 {
9 public:
10 Channel(int epollfd, int sockfd);
11 ~Channel();
12 void setCallBack(IChannelCallBack* callBack);
13 void handleEvent();
14 void setRevents(int revent);
15 int getSockfd();
16 void enableReading();
17 private:
18 void update();
19 int _epollfd;
20 int _sockfd;
21 int _events;
22 int _revents;
23 IChannelCallBack* _callBack;
24 };
按照作者描述"每个Channel对象自始至终只负责一个文件描述符的IO事件分发"。我是这么理解的,Channel把socket文件描述符和关心这个描述符的回调捆绑在了一起,之前的v0.01版本,程序在调用epoll_wait获得事件后,直接就进行了事件处理,现在通过添加Channel,程序终于可以将事件处理程序写在一个单独的函数中,然后将这个函数注册到Channel上。这个注册的过程比较关键,在TcpServer.cc的117和118行,下面这两句调用
[cpp] view
plaincopyprint?
117 pChannel->setCallBack(this);
118 pChannel->enableReading();
117行负责把回调指针传递给Channel(muduo使用的是boost::function),这个指针指向实现IChannelCallback接口的一个对象,也就是TcpServer本身了。setCallBack只是将这个指针保存在成员变量_callBack中,等待后续调用。
118行enableReading()的实现有两步,首先将_events(注意和_revents区别)里加入EPOLLIN标记,然后通过update()将事件真正注册到epollfd上,这是最关键的步骤。调用epoll_ctl注册的过程和v0.01版本有微小的差别,这个差别也很关键。
v0.01版本是这样的
[cpp] view
plaincopyprint?
50 ev.data.fd = listenfd;
51 ev.events = EPOLLIN;
52 epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev);
当前版本
[cpp] view
plaincopyprint?
45 struct epoll_event ev;
46 ev.data.ptr = this;
47 ev.events = _events;
48 epoll_ctl(_epollfd, EPOLL_CTL_ADD, _sockfd, &ev);
差别在于ev.data.fd = listenfd 和 ev.data.ptr = this
使用man epoll_ctl来查看一下epoll_event的定义,data字段是一个union,所以可以存放任何64位长度的内容。
[cpp] view
plaincopyprint?
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
data字段的作用是当事件发生后,可以让epoll的使用者获得这个事件的信息,v0.01版本只保存了一个sockef描述符在里面,这样我们必须通过再额外建立一个"描述符->回调"映射才能找到注册的函数,然后去调用它。v0.03直接将Channel的指针保存到data字段中,直接解决了这个问题,因为一个Channel对象可以保存任意的信息,回调放到其成员变量中即可,当然了Channel中也保存着socket描述符。
在epoll_wait()返回事件后,有两块逻辑要执行
[cpp] view
plaincopyprint?
122 vector<Channel*> channels;
123 int fds = epoll_wait(_epollfd, _events, MAX_EVENTS, -1);
... ...
129 for(int i = 0; i < fds; i++)
130 {
131 Channel* pChannel = static_cast<Channel*>(_events[i].data.ptr);
132 pChannel->setRevents(_events[i].events);
133 channels.push_back(pChannel);
134 }
135
136 vector<Channel*>::iterator it;
137 for(it = channels.begin(); it != channels.end(); ++it)
138 {
139 (*it)->handleEvent();
140 }
第一步129行到134行 ,遍历所有的事件,从其data字段中拿出和这个socket相关的Channel指针,并且将其_revents(注意和_events区别)字段填充好,最后将Channel插入到vector中
第二步136行到140行,遍历vector,逐一调用其中的handleEvent方法。handleEvent方法里会直接调用_callBack的OnIn方法,把事件送给注册好的回调进行处理。
这里之所以分成两个步骤而不是一边遍历fds一边调用handleEvent(),是由于后者会添加或删除Channel,从而造成fds在遍历期间改变大小,这是非常危险的。作者在书中有提及(P285)。
v0.03版本重要修改介绍完了,下面是一些小改动和注意事项
1加入了防止头文件重复引用的#ifndef系列宏
2加入前置声明 和专门用于前置声明的Declear.h和宏定义Define.h
3 引入了内存泄漏,代码中有注释。
经过这个版本,代码的问题还很多,后续改进。
version 0.00 从epoll构建muduo-1 mini-muduo介绍
version 0.01 从epoll构建muduo-2 最简单的epoll
version 0.02 从epoll构建muduo-3 加入第一个类,顺便介绍reactor
version 0.03 从epoll构建muduo-4 加入Channel
version 0.04 从epoll构建muduo-5 加入Acceptor和TcpConnection
version 0.05 从epoll构建muduo-6 加入EventLoop和Epoll
version 0.06 从epoll构建muduo-7 加入IMuduoUser
version 0.07 从epoll构建muduo-8 加入发送缓冲区和接收缓冲区
version 0.08 从epoll构建muduo-9 加入onWriteComplate回调和Buffer
version 0.09 从epoll构建muduo-10 Timer定时器
version 0.11 从epoll构建muduo-11 单线程Reactor网络模型成型
version 0.12 从epoll构建muduo-12 多线程代码入场
version 0.13 从epoll构建muduo-13 Reactor + ThreadPool 成型
mini-muduo v 0.03版本,这是个版本最重要的修改是加入了一个名为Channel的类。完整可运行的示例可从github下载,使用命令git checkout v0.03可切换到此版本,在线浏览此版本到这里
介绍一下Channel类,先看其声明,这里特别要注意_events和_revents,前者是要关注的事件,后者是发生的事件,不仔细看容易混淆。名字的来源是poll(2)的struct pollfd
[cpp] view
plaincopyprint?
7 class Channel
8 {
9 public:
10 Channel(int epollfd, int sockfd);
11 ~Channel();
12 void setCallBack(IChannelCallBack* callBack);
13 void handleEvent();
14 void setRevents(int revent);
15 int getSockfd();
16 void enableReading();
17 private:
18 void update();
19 int _epollfd;
20 int _sockfd;
21 int _events;
22 int _revents;
23 IChannelCallBack* _callBack;
24 };
按照作者描述"每个Channel对象自始至终只负责一个文件描述符的IO事件分发"。我是这么理解的,Channel把socket文件描述符和关心这个描述符的回调捆绑在了一起,之前的v0.01版本,程序在调用epoll_wait获得事件后,直接就进行了事件处理,现在通过添加Channel,程序终于可以将事件处理程序写在一个单独的函数中,然后将这个函数注册到Channel上。这个注册的过程比较关键,在TcpServer.cc的117和118行,下面这两句调用
[cpp] view
plaincopyprint?
117 pChannel->setCallBack(this);
118 pChannel->enableReading();
117行负责把回调指针传递给Channel(muduo使用的是boost::function),这个指针指向实现IChannelCallback接口的一个对象,也就是TcpServer本身了。setCallBack只是将这个指针保存在成员变量_callBack中,等待后续调用。
118行enableReading()的实现有两步,首先将_events(注意和_revents区别)里加入EPOLLIN标记,然后通过update()将事件真正注册到epollfd上,这是最关键的步骤。调用epoll_ctl注册的过程和v0.01版本有微小的差别,这个差别也很关键。
v0.01版本是这样的
[cpp] view
plaincopyprint?
50 ev.data.fd = listenfd;
51 ev.events = EPOLLIN;
52 epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &ev);
当前版本
[cpp] view
plaincopyprint?
45 struct epoll_event ev;
46 ev.data.ptr = this;
47 ev.events = _events;
48 epoll_ctl(_epollfd, EPOLL_CTL_ADD, _sockfd, &ev);
差别在于ev.data.fd = listenfd 和 ev.data.ptr = this
使用man epoll_ctl来查看一下epoll_event的定义,data字段是一个union,所以可以存放任何64位长度的内容。
[cpp] view
plaincopyprint?
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
struct epoll_event {
uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
data字段的作用是当事件发生后,可以让epoll的使用者获得这个事件的信息,v0.01版本只保存了一个sockef描述符在里面,这样我们必须通过再额外建立一个"描述符->回调"映射才能找到注册的函数,然后去调用它。v0.03直接将Channel的指针保存到data字段中,直接解决了这个问题,因为一个Channel对象可以保存任意的信息,回调放到其成员变量中即可,当然了Channel中也保存着socket描述符。
在epoll_wait()返回事件后,有两块逻辑要执行
[cpp] view
plaincopyprint?
122 vector<Channel*> channels;
123 int fds = epoll_wait(_epollfd, _events, MAX_EVENTS, -1);
... ...
129 for(int i = 0; i < fds; i++)
130 {
131 Channel* pChannel = static_cast<Channel*>(_events[i].data.ptr);
132 pChannel->setRevents(_events[i].events);
133 channels.push_back(pChannel);
134 }
135
136 vector<Channel*>::iterator it;
137 for(it = channels.begin(); it != channels.end(); ++it)
138 {
139 (*it)->handleEvent();
140 }
第一步129行到134行 ,遍历所有的事件,从其data字段中拿出和这个socket相关的Channel指针,并且将其_revents(注意和_events区别)字段填充好,最后将Channel插入到vector中
第二步136行到140行,遍历vector,逐一调用其中的handleEvent方法。handleEvent方法里会直接调用_callBack的OnIn方法,把事件送给注册好的回调进行处理。
这里之所以分成两个步骤而不是一边遍历fds一边调用handleEvent(),是由于后者会添加或删除Channel,从而造成fds在遍历期间改变大小,这是非常危险的。作者在书中有提及(P285)。
v0.03版本重要修改介绍完了,下面是一些小改动和注意事项
1加入了防止头文件重复引用的#ifndef系列宏
2加入前置声明 和专门用于前置声明的Declear.h和宏定义Define.h
3 引入了内存泄漏,代码中有注释。
经过这个版本,代码的问题还很多,后续改进。
相关文章推荐
- DOS命令taskkill
- 如何安装cocoaPods 默认源 终端 sudo gem install cocoapods
- VS2008 C++ 调用托管C++dll 当前不会命中断点,没有与此关联的代码
- 怎样成为一个高级JAVA工程师
- android 软键盘隐藏
- Eclipse 最牛的几款插件
- 当Navicat for Mysql出现 1045 -Access denied for user'root'@'ipAddress'(using password :yes)
- 单例模式 理解,简单通透
- BZOJ3144: [Hnoi2013]切糕
- Java网络编程以及简单的聊天程序
- PowerShell检测并添加用户权限
- 使用reids结合wcf实现集群模式下的聊天室功能
- (六十四)第四章编程练习
- 统计大写,小写,数字等个数
- Matlab GUI对话框操作
- jvm学习笔记1:JVM内存数据区域介绍
- Atom
- 浅谈java volatile
- 正则表达式的几个实际应用
- Java编程的环境变量配置