day5-channel的使用
回忆一下,之前关于epoll的使用都是将某个socket加入到红黑树中,但是我们从来没在乎过事件是什么,事件需要我们做什么处理,今天要加入的channel就是为了能区分事件类型,那么接下来看看channel是怎么做的吧。
1、错误检测机制
不谈,没变化
2、创建地址
不谈,没变化
3、socket创建
不谈,没变化
4、并发epoll
首先来看一下,并发epoll有什么差别,可以看出来多了一个updateChannel方法,并将poll的返回值做了改变,那么来看看具体实现。
//未加channel
class Epoll
{
private:
int epfd;
struct epoll_event *events;
public:
Epoll();
~Epoll();
void addFd(int fd, uint32_t op);
std::vector<epoll_event> poll(int timeout = -1);
};
//加channel
class Channel;
class Epoll
{
private:
int epfd;
struct epoll_event *events;
public:
Epoll();
~Epoll();
void addFd(int fd, uint32_t op);
void updateChannel(Channel*);
std::vector<Channel*> poll(int timeout = -1);
};
关于构造函数和析构函数的实现,毫无区别,毫无区别,说明本质上epoll仍旧没有改变。
将事件添加到epoll中的函数addFd,毫无变化,说明树上的东西也是没有变化的。
接下来就是channel所带来的变化了,这对于理解channel是什么很有帮助,我浅显地认为只有明白了怎么应用才能知道其实际意义。
来看一下poll方法的前后对比:
//加入channel前
std::vector<epoll_event> Epoll::poll(int timeout){
std::vector<epoll_event> activeEvents;
int nfds = epoll_wait(epfd, events, MAX_EVENTS, timeout);
errif(nfds == -1, "epoll wait error");
for(int i = 0; i < nfds; ++i){
activeEvents.push_back(events[i]);
}
return activeEvents;
}
//加入channel后
std::vector<Channel*> Epoll::poll(int timeout){
std::vector<Channel*> activeChannels;
int nfds = epoll_wait(epfd, events, MAX_EVENTS, timeout);
errif(nfds == -1, "epoll wait error");
for(int i = 0; i < nfds; ++i){
Channel *ch = (Channel*)events[i].data.ptr;
ch->setRevents(events[i].events);
activeChannels.push_back(ch);
}
return activeChannels;
}
差别主要在返回的事件池及添加事件的方法,这里我们要看一下对channel做了什么操作,首先是对当前事件创建一个ch指针指向事件data部分的ptr,并通过setRevents记录事件。
考虑一下为什么这样做,ch指针指向了事件类型,setRevents记录了事件,我们是不是可以在知道了socket就按照事件类型直接处理呢?
接下来是个新出现的方法updateChannel,其是将channel解出来添加到epoll,首先获取channel的socket,并获得ev事件将事件添加到epoll将标志位进行设置。
这里我们彻底搞清楚了channel是什么,channel将socket和ev合并了构成了一个新的结构体。
void Epoll::updateChannel(Channel *channel){
int fd = channel->getFd();
struct epoll_event ev;
bzero(&ev, sizeof(ev));
ev.data.ptr = channel;
ev.events = channel->getEvents();
if(!channel->getInEpoll()){
errif(epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev) == -1, "epoll add error");
channel->setInEpoll();
// debug("Epoll: add Channel to epoll tree success, the Channel's fd is: ", fd);
} else{
errif(epoll_ctl(epfd, EPOLL_CTL_MOD, fd, &ev) == -1, "epoll modify error");
// debug("Epoll: modify Channel in epoll tree success, the Channel's fd is: ", fd);
}
}
这样socket不再和事件割裂,通过获取套接字也能获取事件信息。
5、channel
那么思考一下channel需要哪些变量和函数。
首先需要channel所期望能存在的epoll、socket、当前事件以及一个标志位(这是由于我们向epoll添加的仍然是ev,channel是否添加我们需要标志位来告诉我们)
其次构造函数、析构函数、获取socket、获取/设置当前事件、获取/设置标志位。
那么我们来看看声明,不好,出现了我们没想到变量和函数,那我们在实现上看看其有什么作用。
class Epoll;
class Channel
{
private:
Epoll *ep;
int fd;
uint32_t events;
uint32_t revents;
bool inEpoll;
public:
Channel(Epoll *_ep, int _fd);
~Channel();
void enableReading();
int getFd();
uint32_t getEvents();
uint32_t getRevents();
bool getInEpoll();
void setInEpoll();
// void setEvents(uint32_t);
void setRevents(uint32_t);
};
首先是构造/析构函数,利用参数列表构造了一下子告诉了我们所属的epoll和socket
Channel::Channel(Epoll *_ep, int _fd) : ep(_ep), fd(_fd), events(0), revents(0), inEpoll(false){
}
Channel::~Channel()
{
}
之后是没见过的函数enableReading,原来是一个将事件设置为边缘触发,那么说明events 是当前事件,并且在设置好events后立马将事件加入到了epoll中。
void Channel::enableReading(){
events = EPOLLIN | EPOLLET;
ep->updateChannel(this);
}
之后就是获取/设置各类信息的方法
int Channel::getFd(){
return fd;
}
uint32_t Channel::getEvents(){
return events;
}
uint32_t Channel::getRevents(){
return revents;
}
bool Channel::getInEpoll(){
return inEpoll;
}
void Channel::setInEpoll(){
inEpoll = true;
}
void Channel::setRevents(uint32_t _ev){
revents = _ev;
}
6、服务器
没发生什么变化的部分先罗列出来
Socket *serv_sock = new Socket();
InetAddress *serv_addr = new InetAddress("127.0.0.1", 8888);
serv_sock->bind(serv_addr);
serv_sock->listen();
Epoll *ep = new Epoll();
serv_sock->setnonblocking();
原本创建事件的部分变成了创建channel,新建了一个channel并将channel中的事件放到epoll。
Channel *servChannel = new Channel(ep, serv_sock->getFd());
servChannel->enableReading();
这里将之前代码中的使用event的部分改为使用channel就可以了,注意这里调用了poll,不要忘记在poll中我们将事件值赋给了revents,那么event是事件的属性,revent是对外的接口。
while(true){
std::vector<Channel*> activeChannels = ep->poll();
int nfds = activeChannels.size();
for(int i = 0; i < nfds; ++i){
int chfd = activeChannels[i]->getFd();
if(chfd == serv_sock->getFd()){ //新客户端连接
InetAddress *clnt_addr = new InetAddress(); //会发生内存泄露!没有delete
Socket *clnt_sock = new Socket(serv_sock->accept(clnt_addr)); //会发生内存泄露!没有delete
printf("new client fd %d! IP: %s Port: %d\n", clnt_sock->getFd(), inet_ntoa(clnt_addr->addr.sin_addr), ntohs(clnt_addr->addr.sin_port));
clnt_sock->setnonblocking();
Channel *clntChannel = new Channel(ep, clnt_sock->getFd());
clntChannel->enableReading();
} else if(activeChannels[i]->getRevents() & EPOLLIN){ //可读事件
handleReadEvent(activeChannels[i]->getFd());
} else{ //其他事件,之后的版本实现
printf("something else happened\n");
}
}
}
delete serv_sock;
delete serv_addr;
return 0;
理清楚了,就这样,有不对的地方大家可以讨论,我也是初学者会积极学习提升的,请大佬们指正。