文章目录
前言
今天我们所要学习的epoll作为多路转接的后起之秀,会弥补之前所讲的select和poll的所有缺点,可以说从思想上,epoll与select、poll完全不同。
一、epoll接口
int epoll_create(int size);
- 作用:创建epoll并返回创建的epoll的文件描述符。
- 参数int size:epoll可以管理的fd数量。
- 返回值:会返回一个文件描述符,这是epoll的fd。 从这里就可以看出epoll的不同点。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
- 作用:根据op操作选项对fd进行内核态的控制。
- 参数int epfd:就是epoll_create创建的epoll的文件描述符。
- 参数int op,有三种操作选项EPOLL_CTL_ADD、EPOLL_CTL_MOD、EPOLL_CTL_DEL。分别对应想epoll添加要关心的fd和其对应事件、修改fd要关心的对应事件、删除epoll要关心的fd。
- 参数struct epoll_event *event,该结构体包含两个成员,events就是要关心的事件,data是一个联合体我们今天使用fd就可以。
- 返回值:成功为0,失败为-1.
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
这个函数的功能就与select、poll一样了。
- 参数int epfd:就是epoll_create创建的epoll的文件描述符。
- 参数struct epoll_event *events:传的是一个数组,同时也是一个输出型参数,用来保存从已就绪队列中拿出的就绪事件。
- 参数int maxevents:events的元素数量。
- 参数int timeout:与select、poll的timeout功能一致。
- 返回值:返回已就绪事件的个数,若timeout则返回0,出错误返回-1。
二、如何理解epoll
epoll与select、poll有很大的不同。
首先在创建epoll时,直接给epoll创建了一个文件描述符,我们就可以将epoll视为一个文件,其对应的struct file里就有指针指向一个独特的epoll数据结构!(内核中)
该数据结构如下
它最重要的是两个部分,这两个部分分别是一颗红黑树,一个队列。
这棵红黑树用来保存所有需要关心的fd和其对应事件,当驱动层(有可能是键盘驱动,也有可能是网卡驱动)有数据就绪时,会通过设置好的callback回调函数,先将数据向上交付,再去查找这棵rb_tree有没有对应的fd和关心事件,如果有,则构建新节点将就绪事件插入到就绪队列当中,最后通过epoll_wait中的输出型参数events将事件从内核层的就绪队列拿到用户层。
三、epoll示例代码
Epoll.hpp
#include "log.hpp"
#include <sys/epoll.h>
#include <functional>
#include <memory>
#define MAX_EPOLLFDS 1024
class Epoller
{
public:
Epoller() {}
void Init()
{
_epfd = epoll_create(MAX_EPOLLFDS);
if (_epfd < 0)
{
lg(Fatal, "Epoll Create Error...");
exit(1);
}
else
{
lg(Info, "Epoll Create Succeed, epfd:%d", _epfd);
}
}
int EpollWait(epoll_event *events, int num, int timeout)
{
int n = epoll_wait(_epfd, events, num, timeout);
return n;
}
void EpollerUpdate(int op, int fd, uint32_t events)
{
if (op == EPOLL_CTL_DEL)
{
int n = epoll_ctl(_epfd, op, fd, nullptr);
if (n < 0)
{
lg(Warning, "Epoll Delete Error, fd:%d", fd);
return;
}
else
{
lg(Info, "Epoll Delete Succeed, fd:%d", fd);
return;
}
}
struct epoll_event tmp;
tmp.data.fd = fd;
tmp.events = events;
int n = epoll_ctl(_epfd, op, fd, &tmp);
if (n < 0)
{
lg(Warning, "Epoll Control Error, fd:%d", fd);
}
else
{
lg(Info, "Epoll Control Succeed, fd:%d", fd);
}
}
~Epoller()
{
if (_epfd != -1)
{
close(_epfd);
}
}
private:
int _epfd;
};
EpollServer.hpp
#include "Socket.hpp"
#include "Epoll.hpp"
#define EVENT_IN EPOLLIN
const std::string default_ip = "0.0.0.0";
const uint16_t default_port = 8080;
class EpollServer
{
static const int num = 128;
public:
EpollServer(uint16_t port = default_port)
: _port(port), _listensock(new Socket), _epoller(new Epoller) {}
void Init()
{
_epoller->Init();
_listensock->Init();
_listensock->Bind(AF_INET, default_ip, _port);
_listensock->Listen();
_epoller->EpollerUpdate(EPOLL_CTL_ADD, _listensock->_sockfd, EVENT_IN);
}
void Accepter()
{
int newfd = _listensock->Accept();
if (newfd == -1)
return;
_epoller->EpollerUpdate(EPOLL_CTL_ADD, newfd, EVENT_IN);
}
void Handler(int fd)
{
char buffer[1024];
memset(buffer, 0, sizeof buffer);
int n = read(fd, buffer, sizeof buffer - 1);
if (n > 0)
{
buffer[n] = 0;
std::string mes = buffer;
std::cout << mes;
}
else if (n < 0)
{
lg(Warning, "Read Error...");
_epoller->EpollerUpdate(EPOLL_CTL_DEL, fd, 0);
close(fd);
}
else
{
lg(Info, "Foreign Host Closed...");
_epoller->EpollerUpdate(EPOLL_CTL_DEL, fd, 0);
close(fd);
}
}
void Start()
{
struct epoll_event events[num];
while (1)
{
int n = _epoller->EpollWait(events, num, 5000);
if (n > 0)
{
for (int i = 0; i < n; ++i)
{
if (events->data.fd == _listensock->_sockfd)
{
// accept
Accepter();
continue;
}
Handler(events->data.fd);
}
}
}
}
~EpollServer()
{
_listensock->Close();
}
private:
std::unique_ptr<Socket> _listensock;
uint16_t _port;
std::unique_ptr<Epoller> _epoller;
};