仿muduo庫實現并發服務器
- 1.Poller模塊
- 成員變量
- 創建epoll模型
- 對于一個描述符添加或修改事件監控
- 對于一個描述符移除事件監控
- 啟動epoll事件監控,獲取所有活躍連接
1.Poller模塊
Poller模塊主要是對任意的描述符進行IO事件監控。
它是對epoll的封裝,可以讓對描述符進行事件監控的操作更加簡單。
它與Channel的關系:
一旦有活躍事件了,只有獲取到這個連接的Channel對象,才能知道該連接要監控的事件是什么,知道事件就緒如何去處理。
主要的流程:
1.對描述符進行監控,但是要注意監控的對象是channel對象,因為只有channel對象才能找到該描述符要監控什么事件。
2.當描述符就緒時,通過描述符映射找到channel對象(通過hash找到channel對象),因為只有找到channel對象,才能知道就緒的事件如何處理。所以當描述符就緒時,就返回對應的channel對象。
成員變量
private:int _epfd; // 用來標識epoll模型struct epoll_event _evs[DEFALUTMAX]; // 用來保存所有就緒的事件信息,信息1:就緒的描述符fd是誰。信息2:就緒的事件是什么std::unordered_map<int, Channel *> _channel; // 用來保存所有要監控的描述符信
1.poller是封裝epoll對描述符進行操作,所以一定要有一個epoll句柄。
2.當事件就緒時,就緒的事件信息就會保存在struct epoll_event 結構體數據中,保存的信息有就緒的描述符fd,就緒的事件。
3.poller是用來監控管理所有要監控的描述符,所以需要一個容器保存所有監控的描述符。
而poller監控的對象是channel,所以使用hash通過描述符映射對應的channel。這樣就可以保存管理所有監控的描述符對象。
創建epoll模型
創建epoll模型,使用epoll_create()接口,返回值就是對應的epoll句柄。
// 創建epoll模型Poller(){_epfd = epoll_create(DEFALUTMAX); // 創建一個epoll模型if (_epfd < 0){ERRLog("epoll create failed");abort(); // 這個出錯了就直接退出}}
對于一個描述符添加或修改事件監控
對描述符進行添加或修改事件監控,需要使用的接口是epoll_ctl(),不過這里對epoll_ctl()的操作先封裝起來,外部直接調用這個封裝好的epoll_ctl接口
要想對一個描述符添加事件監控,首先需要知道該描述符的fd,要監控的事件是什么,要進行操作是什么等細節。而描述符的fd,以及要監控的事件是什么都在channel對象里,所以傳入一個channel對象,以及所需要進行操作即可。
不過在對一個連接真正添加和修改或者移除事件之前,還是需要判斷一個該連接是否已經被添加到內核里面去了,如果已經被添加進去了,那么要進行的操作就是更新修改了,就不是添加了。所以我們還需要封裝一個判定是否已經設置了監控的接口。
如何判斷呢?就判斷管理的哈希表中是否存在對于的channel對象即可。
// 直接對epoll_ctl進行操作void Update(Channel *channel, int op){int fd = channel->Fd();//獲取該channnel對應的描述符fdstruct epoll_event ev;ev.data.fd = fd;//設置要關系的fdev.events = channel->Events();//設置要關系的事件int ret = epoll_ctl(_epfd, op, fd, &ev);//對于一個描述符進行添加或修改事件監控if (ret < 0){ERRLog("epoll_ctl failed");}}// 判斷一個連接channel是否設置了事件監控,就看_channel中是否有該連接bool HasChannel(Channel *channel){auto it = _channel.find(channel->Fd());if (it == _channel.end())return false; // 沒找到return true;}
所以在內部封裝了兩個接口,對外真正提供的接口則是:
// 對于一個描述符添加或修改事件監控void UpdateEvent(Channel *channel){// 首先判斷該連接是否已經被設置監控了,如果沒有則添加監控,如果有則進行修改if (HasChannel(channel) == false){_channel.insert(std::make_pair(channel->Fd(), channel));//_channel[channel->Fd()]=channel;Update(channel, EPOLL_CTL_ADD);//}else{Update(channel, EPOLL_CTL_MOD);}}
對于一個描述符移除事件監控
對于描述符進行移除事件監控,主要有兩個步驟:
1.從管理的hash表里移除(erase)
2.從紅黑色樹上移除(epoll_ctl,DEL)
// 對于一個描述符移除事件監控void RemoveEvent(Channel *channel){auto it = _channel.find(channel->Fd());//在哈希表中找到該channel對象if (it != _channel.end()){_channel.erase(it);//從哈希表中刪除該channnel對象}Update(channel, EPOLL_CTL_DEL);//從內核中刪除該監控}
啟動epoll事件監控,獲取所有活躍連接
啟動事件監控,就是epoll進行等待事件就緒。
當有事件就緒時,epoll就不再等待,就會返回,并將就緒的信息帶回來。
就緒的信息有哪個文件描述符的什么事件就緒了,光有這些信息是沒有用的,因為對于描述符的channel對象不知道什么事件就緒啊,只有channel對象才能知道就緒后該怎么操作。所以需要將該描述符的什么事件就緒信息設置進對應的channel對象中(通過描述符在哈希表中映射找到channel對象),這樣channel對象才能知道它就緒了什么事件,并且可以進行對應的處理,
所以我們可以通過一個vector數組,將所有就緒的channel對象全部存儲起來。
供外層eventloop取出,全部依次執行對應的操作。
操作:
1.epoll進行等待監控
2.epoll返回,將就緒的信息設置到對應的channel對象中
3.保存所有就緒的channel對象。
通過一個輸出型參數將數據帶出來。
// 開始監控,并返回所有活躍連接void Poll(std::vector<Channel *> *active){int nfds = epoll_wait(_epfd, _evs, DEFALUTMAX, -1); // 默認為阻塞等待if (nfds < 0){if (errno == EINTR){return;}ERRLog("epoll wait failed:%s\n", strerror(errno));abort();}for (int i = 0; i < nfds; i++)//有事件就緒了{int fd = _evs[i].data.fd; // 活躍的fd是哪個、assert(_channel.find(fd) != _channel.end()); // 不能在管理的channel里找不到// 找到之后就將就緒的事件信息設置到對應的channel里_channel[fd]->SetRevents(_evs[i].events);active->push_back(_channel[fd]); // 將活躍的連接插入進去,保存起來}}