目录

  • 理解epoll工作原理
    • epoll的两种工作模式
  • 如何使用epoll
  • epoll的优点
  • 使用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 5using 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 64class Reactor;
class EventItem;typedef int(*callback_t)(EventItem *);每一个事件都要对应一个这个结构体
class EventItem
{public://定义回调函数callback_t recv_handler;callback_t send_handler;//sockint sock;//缓存string inbuffer;string outbuffer;//回指向ReactorReactor* 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=&Re;把监听套接字设置成非阻塞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相关推荐

  1. epoll原理_如果这篇文章说不清epoll的本质,那就过来掐死我吧! (1)

    转载地址:https://zhuanlan.zhihu.com/p/63179839 从事服务端开发,少不了要接触网络编程.epoll作为linux下高性能网络服务器的必备技术至关重要,nginx.r ...

  2. Epoll的本质(内部实现原理)

    转自:https://zhuanlan.zhihu.com/p/63179839 作者:罗培羽 从事服务端开发,少不了要接触网络编程.epoll作为linux下高性能网络服务器的必备技术至关重要,ng ...

  3. epoll为什么这么快,epoll的实现原理

    这是我看过的最通俗易懂的关于epoll的讲解: 一.为什么epoll这么快: epoll是多路复用IO(I/O Multiplexing)中的一种方式,但是仅用于linux2.6以上内核,在开始讨论这 ...

  4. 基础技术 - 如果这篇文章说不清epoll的本质,那就过来掐死我吧!

    本文主体转自https://zhuanlan.zhihu.com/p/63179839,加上了自己的理解和批注 从事服务端开发,少不了要接触网络编程.epoll作为linux下高性能网络服务器的必备技 ...

  5. 知乎大佬图文并茂的epoll讲解,看不懂的去砍他

    select.poll.epoll的文章很多,自己也看过不少经典好文.不过第一次看到讲的如此通俗易懂.又图文并茂的.因此拿来分享下,供后续翻看学习. 原文链接:https://zhuanlan.zhi ...

  6. Linux中select poll和epoll的区别

    首先给大家分享一个巨牛巨牛的人工智能教程,是我无意中发现的.教程不仅零基础,通俗易懂,而且非常风趣幽默,还时不时有内涵段子,像看小说一样,哈哈-我正在学习中,觉得太牛了,所以分享给大家!点这里可以跳转 ...

  7. epoll 的本质是什么?

    从事服务端开发,少不了要接触网络编程.epoll 作为 Linux 下高性能网络服务器的必备技术至关重要,nginx.Redis.Skynet 和大部分游戏服务器都使用到这一多路复用技术. epoll ...

  8. 从根上理解高性能、高并发(六):通俗易懂,高性能服务器到底是如何实现的

    本文原题"高并发高性能服务器是如何实现的",转载请联系作者. 1.系列文章引言 1.1 文章目的 作为即时通讯技术的开发者来说,高性能.高并发相关的技术概念早就了然与胸,什么线程池 ...

  9. Java注解---通俗易懂

    本文转载于Java注解-最通俗易懂的注解 Annotation 中文译过来就是注解.标释的意思,在 Java 中注解是一个很重要的知识点,但经常还是有点让新手不容易理解. 我个人认为,比较糟糕的技术文 ...

最新文章

  1. eclipse修改文件代码不起作用,输出时还是老的,估计是缓存问题
  2. 关于linux内核模块的装载过程
  3. java计算字符串中字符出现的次数_java – 计算字符串中字符出现次数
  4. Python 字符串(二)
  5. Virtualbox 2.1突发性错误解决办法(也许是BUG)
  6. RSA 2019安全大会:企业资产管理成行业新风向标,云上安全占绝对优势...
  7. 这些年看过的书...
  8. 大一c语言实验调试步骤,大一c语言实验报告.docx
  9. (附源码)Springboot通用办事流程管理软件 毕业设计 211819
  10. 微星MSI-GP65 Ubuntu Linux驱动 ALC 1220声卡
  11. Linux权限与sudo
  12. 提醒专注:既往不恋,未来不迎,当下不杂
  13. Java进阶(一) Java高效读取大文件,占内存少
  14. 怎么使用电脑打开手机分享的vcf联系人文件
  15. 《吉他自学三月通》学习指导
  16. 密西西比河谷州立大学:Android应用程序开发(五)
  17. C语言,数组的类型,大小
  18. 当前已提供的各国NTP服务器列表
  19. mysql数据库5.7版本二进制安装与破解mysql数据库密码
  20. socketio使用

热门文章

  1. 《Three.js 开发指南》源码示例说明以及在线demo(原书第二版)附第三版的代码下载
  2. funny_upload
  3. 此计算机上无法找到autocad2017,无法安装cad2017,电脑提示无法定位inf的修复方法...
  4. git推送代码详细教程
  5. psycopg2.errors.DatatypeMismatch: 错误: 无法实现外键约束 “sale_an_product_tax_id_fkey“
  6. RT_Thread_空闲线程
  7. Thinking_2_酸甜苦辣咸的2016
  8. Android 屏蔽锁屏界面上的通知显示
  9. UEFI是什么意思?UEFI和BIOS的区别是什么?
  10. 【檀越剑指大厂—SpringMVC】SpringMVC篇