通俗易懂的epoll
Posted 世_生
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了通俗易懂的epoll相关的知识,希望对你有一定的参考价值。
目录
理解epoll工作原理
- 每一个epoll对象都有eventepoll结构体
- epoll底层是一颗红黑数来管理文件描述符中的事件。
- 而所有添加到epoll中的事件都会与设备(网卡)驱动程序建立回调关系,也就是说,当响应的事件发生时会调用这个回调方法
- 当事件发生时,回调方法会拷贝一份该节点到一个队列中,该队列的用双链表实现的。
- 在epoll中没一个事件都会建立节点(epitem结构体)
例子:我们在编写套接字编程的代码时,要把监听套接字放在epoll中来,让epoll帮我们来进行等待连接到来,连接到来事件叫做读事件。
这时候我们通过调用函数把监听套接字放入到红黑树中,Linux内核会对每一个事件对象创建一个epintm结构体。
判断也没有事件只需要通过rdllist是否为空。
struct epitem
struct rb_node rbn;//红黑树节点
struct list_head rdllink;//双向链表节点
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所属的eventpoll对象
struct epoll_event event; //期待发生的事件类型
epoll的两种工作模式
LT:水平触发,不断的提醒你有事件到来,直到你把事件全部执行完
ET:边缘触发:只提醒你一次有事件到来,如果不执行,就要等下一次事件到来。
ET模式下:要采用非阻塞的方式进行读操作。且要不断的读,直到读完。
如果不采用非阻塞方式读取,则可能会阻塞住。
如何使用epoll
创建句柄
int epoll_create(int size);
返回一个整数,是一个文件描述符。
使用:int epfd=epoll_create(256);
添加事件到红黑树中
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
op:是一个宏
EPOLL_CTL_ADD:添加
EPOLL_CTL_MOD:修改
EPOLL_CTL_DEL:删除
struct epoll_event
uint32_t events; //事件
epoll_data_t data; /* User data variable */
typedef union epoll_data
void *ptr;
int fd; //文件描述符
uint32_t u32;
uint64_t u64;
epoll_data_t;
一般events填EPOLLIN(读事件)、EPOLLOUT(写事件)。
返回值:成功返回0,失败返回-1
//拿出事件
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
timeout:轮询检查的事件
返回值:成功返回事件到达的数量。
使用:
int epfd=epoll_create(256);
struct epoll_event item;
item.data.fd=sock; //添加套接字
item.events=EPOLLIN //只关心读时间
epoll_ctl(epfd,EPOLL_CTL_ADD,sock,&item);//添加事件到红黑树
struct events eve[64];
int num=epoll_wait(epfd,eve,64,1000);
//有事件到来,会把事件的节点拷贝到eve数组中,
//我们只需要遍历数组就可以进行读或者写。
for(int i=0;i<num;i++)
if(eve[i].events & EPOLLIN)
int sock=eve[i].data.fd;
开始进行读操作(进行读操作时要区分是连接到来,还只是进行读取)
else if(eve[i].event& EPOLLOUT)
int sock=eve[i].data.fd;
开始进行写操作
epoll的优点
- 接口使用方便: 虽然拆分成了三个函数, 但是反而使用起来更方便高效. 不需要每次循环都设置关注的文件描述符, 也做到了输入输出参数分离开
- 数据拷贝轻量: 只在合适的时候调用 EPOLL_CTL_ADD 将文件描述符结构拷贝到内核中, 这个操作并不频繁(而select/poll都是每次循环都要进行拷贝)
- 事件回调机制: 避免使用遍历, 而是使用回调函数的方式, 将就绪的文件描述符结构加入到就绪队列中,
- epoll_wait 返回直接访问就绪队列就知道哪些文件描述符就绪. 这个操作时间复杂度O(1). 即使文件描述符数目很多, 效率也不会受到影响.
- 没有数量限制: 文件描述符数目无上限
使用epoll实现一个服务器
sock.hpp//创建套接字
#pragma once
#include<iostream>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<unistd.h>
#include<cstring>
#include"log.hpp"
#define LONG 5
using namespace std;
class Socket
public:
static void Sock(int& listen_sock)
listen_sock=socket(AF_INET,SOCK_STREAM,0);
if(listen_sock<0)
LOG(ERROR,"socket error...");
exit(1);
LOG(INFO,"socket success...");
static void Bind(int listen_sock,int port)
struct sockaddr_in own;
memset(&own,0,sizeof(own));
own.sin_family=AF_INET;
own.sin_port=htons(port);
own.sin_addr.s_addr=INADDR_ANY;
if(bind(listen_sock,(struct sockaddr*)&own,sizeof(own))<0)
LOG(error,"bind error...");
exit(2);
LOG(INFO,"bind sucess...");
static void Listen(int listen_sock)
if(listen(listen_sock,LONG)<0)
LOG(error,"listen error...");
exit(3);
LOG(INFO,"listen succsee...");
;
reactor.hpp
#pragma once
#include<iostream>
#include<string>
#include<unordered_map>
#include<unistd.h>
#include<sys/epoll.h>
#include"log.hpp"
using namespace std;
#define MAX_NUM 64
class Reactor;
class EventItem;
typedef int(*callback_t)(EventItem *);
每一个事件都要对应一个这个结构体
class EventItem
public:
//定义回调函数
callback_t recv_handler;
callback_t send_handler;
//sock
int sock;
//缓存
string inbuffer;
string outbuffer;
//回指向Reactor
Reactor* R;
public:
EventItem():sock(0),R(nullptr),recv_handler(nullptr),send_handler(nullptr)
//注册回调函数
void MakeCallBack(callback_t _recv,callback_t _send)
recv_handler=_recv;
send_handler=_send;
~EventItem()
;
class Reactor
private:
//epoll句柄
int epfd;
//使用哈希容器来一一对应
unordered_map<int,EventItem> mp;
public:
Reactor()
void InitReactor()
epfd=epoll_create(256);
if(epfd<0)
LOG(ERROR,"epoll_create error...");
exit(5);
LOG(INFO,"epoll_create success..."+to_string(epfd));
添加事件到红黑树中
void AddToEpoll(int sock,uint32_t ev,const EventItem& item)
struct epoll_event event;
event.events=0;
event.events|=ev;
event.data.fd=sock;
string s=to_string(sock);
if(epoll_ctl(epfd,EPOLL_CTL_ADD,sock,&event)<0)
LOG(ERROR,"epoll_ctl add error...sock:"+s);
else
mp.insert(sock,item);
LOG(INFO,"epoll_ctl add success...sock:"+s);
删除红黑树中的事件
void RevokeEpoll(int sock)
if(epoll_ctl(epfd,EPOLL_CTL_DEL,sock,nullptr)<0)
string s=to_string(sock);
LOG(ERROR,"epoll_ctl del error...sock:"+s);
删除哈希中的映射
mp.erase(sock);
void Assignment(int timeout)
struct epoll_event revs[MAX_NUM];
int num=epoll_wait(epfd,revs,MAX_NUM,timeout);
事件到来轮询recv中的节点
for(int i=0;i<num;i++)
int sock=revs[i].data.fd;
uint32_t mask=revs[i].events;
if(mask & EPOLLIN)
if(mp[sock].recv_handler)
mp[sock].recv_handler(&mp[sock]);调用回调函数进行读
else if(mask & EPOLLOUT)
if(mp[sock].send_handler)
mp[sock].send_handler(&mp[sock]);调用回调函数进行写
~Reactor()
if(epfd>=0)
close(epfd);
;
insertface.hpp
#pragma once
#include<iostream>
#include<cstring>
#include<sys/types.h>
#include<sys/socket.h>
#include"reactor.hpp"
#include"util.hpp"
int recver(EventItem* eve);
连接到来
int accepter(EventItem* eve)
struct sockaddr_in oth;
socklen_t len=sizeof(oth);
int sock=accept(eve->sock,(struct sockaddr*)&oth,&len);
if(sock<0)
else
SelNonBlock(sock);
EventItem item;
item.sock=sock;
item.R=eve->R;
item.MakeCallBack(recver,nullptr);
Reactor* ptr=eve->R;
ptr->AddToEpoll(sock,EPOLLIN|EPOLLET,item);
只写了连接到来的接口,读没有写
int recver(EventItem* eve)
log.hpp//日志,方便知道走到哪里了
#pragma once
#include<iostream>
#include<string>
#include<time.h>
using namespace std;
#define INFO 1
#define ERROR 2
采用宏来调用,具有可读性
#define LOG(str1,str2) log(#str1,str2,__FILE__,__LINE__)
void log(string str1,string str2,string str3,int line)
cout<<"["<<str1<<"]"<<"["<<time(nullptr)<<"]"<<"["<<str2<<"]"<<"["<<str3<<"]"<<"["<<line<<"]"<<endl;
util.hpp
#pragma once
#include<iostream>
#include<unistd.h>
#include<fcntl.h>
设置非阻塞
void SelNonBlock(int sock)
int fl = fcntl(sock, F_GETFL);
fcntl(sock, F_SETFL, fl | O_NONBLOCK);
server.cc
#include<iostream>
#include<cstdlib>
#include"sock.hpp"
#include"reactor.hpp"
#include"insertface.hpp"
#include"util.hpp"
int main(int argc,char* argv[])
if(argc!=2)
exit(4);
int port=atoi(argv[1]);
int listen_sock=-1;
创建套接字
Socket::Sock(listen_sock);
Socket::Bind(listen_sock,port);
Socket::Listen(listen_sock);
创建Reactor,并初始化
Reactor Re;
Re.InitReactor();
创建监听套接字对应的结构
EventItem item;
item.sock=listen_sock;
item.R=ℜ
把监听套接字设置成非阻塞
SelNonBlock(listen_sock);
注册回调函数,我这里把读注册了accept,没有注册写
item.MakeCallBack(accepter,nullptr);
添加到红黑树中,采用ET模式
Re.AddToEpoll(listen_sock,EPOLLIN|EPOLLET,item);
int timeout=1000;
一直调用检查事件是否到来
while(true)
Re.Assignment(timeout);
整个的精髓是,把 epoll 和 读写分开了,采用了回调函数的方法,只不要管reactor.hpp,只需要在insertface.hpp中添加函数就行。
以上是关于通俗易懂的epoll的主要内容,如果未能解决你的问题,请参考以下文章