找工作小项目:day5-channel的使用

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;

理清楚了,就这样,有不对的地方大家可以讨论,我也是初学者会积极学习提升的,请大佬们指正。

  • 6
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值