epoll技术简介
//(2.1)epoll概述
//(1)I/O多路复用:epoll就是一种典型的I/O多路复用技术:epoll技术的最大特点是支持高并发;
//传统多路复用技术select,poll,在并发量达到1000-2000,性能就会明显下降;
//epoll,kquene(freebsd)
//epoll,从linux内核2.6引入的,2.6之前是没有的;
//(2)epoll和kquene技术类似:单独一台计算机支撑少则数万,多则数十上百万并发连接的核心技术;
//epoll技术完全没有这种性能会随着并发量提高而出现明显下降的问题。但是并发没增加一个,必定要消耗一定的内存去保存这个连接相关的数据;
//并发量总还是有限制的,不可能是无限的;
//(3)10万个连接同一时刻,可能只有几十上百个客户端给你发送数据,epoll只处理这几十上百个客户端;
//(4)很多服务器程序用多进程,每一个进程对应一个连接;也有用多线程做的,每一个线程对应 一个连接;
//epoll事件驱动机制,在单独的进程或者单独的线程里运行,收集/处理事件;没有进程/线程之间切换的消耗,高效
//(5)适合高并发,融合epoll技术到项目中,作为大家将来从事服务器开发工作的立身之本;
//写小demo非常简单,难度只有1-10,但是要把epoll技术融合到商业的环境中,那么难度就会骤然增加10倍;
学习epoll要达到的效果及一些说明
//(1)理解epoll的工作原理;面试考epoll技术的工作原理;
//(2)开始写代码
//(3)认可nginx epoll部分源码;并且能复用的尽量复用;
三:epoll原理与函数介绍:三个函数,理解好就等于掌握了epoll技术的工作原理,以下内容务必认真理解。
掌握这个三个函数的使用,并结合网上一些大神关于如何自己实现这个三个函数的源码分析,epoll的三个关键函数都干了什么。
大神源码:
//https://github.com/wangbojing
//a)c1000k_test这里,测试百万并发的一些测试程序;一般以main();
//b)ntytcp:nty_epoll_inner.h,nty_epoll_rb.c
//epoll_create();
//epoll_ctl();
//epoll_wait();
//epoll_event_callback();
//c)总结:建议学习完老师的epoll实战代码之后,再来学习 这里提到的课件代码,事半功倍;
epoll_create()函数
原型 : int epoll_create(int size);
功能:创建一个epoll对象,
返回该对象的描述符【文件描述符】,这个描述符就代表这个epoll对象,后续会用到;
//这个epoll对象最终要用close(),因为文件描述符/句柄 总是关闭的;
参数:size表示你要连接的客户端数量
这个值是建议内核的,假设我们写了100,但是当您的连接数量超过100的时候,内核会给你自动分配
size: >0;
从大神的代码中分析epoll的原理可知:
epoll_create是创建了一个红黑树的节点,
//原理:这个当前看不懂源码也没有关系,先知道大概咋回事,要看懂这块,必须数据结构很明白。
//a)struct eventpoll *ep = (struct eventpoll*)calloc(1, sizeof(struct eventpoll));
//b)rbr结构成员:代表一颗红黑树的根节点[刚开始指向空],把rbr理解成红黑树的根节点的指针;
//红黑树,用来保存 键【数字】/值【结构】,能够快速的通过你给key,把整个的键/值取出来;
//c)rdlist结构成员:代表 一个双向链表的表头指针;
//双向链表:从头访问/遍历每个元素特别快;next。
//d)总结:创建了一个eventpoll结构对象,被系统保存起来;
//rbr成员被初始化成指向一颗红黑树的根【有了一个红黑树】;
//rdlist成员被初始化成指向一个双向链表的根【有了双向链表】;
epoll_ctl 控制某个epoll监控的文件描述符上的事件:注册、修改、删除。也就是说,有了这个红黑树了,就要给树上挂节点了,或者删除节点,或者修改节点了
红黑树上的每一个节点的内容都是如下的一个struct epitem。
每次使用 epoll_ctl实际上就是 将一个struct epitem 从根节点的红黑树上 添加/删除/修改、
原型
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
参数
因此第一个参数是 这个红黑树的根节点,就是告诉系统,我要对这个红黑树进行改动。
第二个参数是说明,我是挂节点/删除节点/修改节点的哪一种
第三个参数是说明,我要挂的节点的 fd,或者删除节点的fd,或者修改节点的fd
第四个参数说明:
我对第三个参数的什么事件进行监控:events表明了是对读:EPOLLIN,写:EPOLLOUT,错误EPOLLERR, 边缘触发模式:EPOLLET
然后做为传入参数,描述了我要做什么事情,里面还有一个fd,对应了epoll_ctl的第三个参数。学到这里的时候有一个想法,就是为什么在还需要第四个参数中有一个fd,且这个fd和第三个参数是一样的,网上也没有找到合理的说明,那么只有一种可能,就是epoll_event 做为参数使用的时候,要用到fd,但是epoll的开发者又不想依赖于外部条件,因此将这个参数再填写一遍
void *ptr //这个todo
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd: 为epoll_creat的句柄
op: 表示动作,用3个宏来表示:
EPOLL_CTL_ADD (注册新的fd到epfd),
EPOLL_CTL_MOD (修改已经注册的fd的监听事件),
EPOLL_CTL_DEL (从epfd删除一个fd);
event: 告诉内核需要监听的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd; // 和 epoll_ctl的第三个参数一样
uint32_t u32; //不用
uint64_t u64; //不用
} epoll_data_t;
events 取值为:
EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT: 表示对应的文件描述符可以写
EPOLLPRI: 表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR: 表示对应的文件描述符发生错误
EPOLLHUP: 表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)而言的
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
参考大神实现epoll_ctl代码:
//原理://a)epi = (struct epitem*)calloc(1, sizeof(struct epitem));//b)epi = RB_INSERT(_epoll_rb_socket, &ep->rbr, epi); 【EPOLL_CTL_ADD】增加节点到红黑树中//epitem.rbn ,代表三个指针,分别指向红黑树的左子树,右子树,父亲;//epi = RB_REMOVE(_epoll_rb_socket, &ep->rbr, epi);【EPOLL_CTL_DEL】,从红黑树中把节点干掉//EPOLL_CTL_MOD,找到红黑树节点,修改这个节点中的内容;//红黑树的节点是epoll_ctl[EPOLL_CTL_ADD]往里增加的节点;面试可能考//红黑树的节点是epoll_ctl[EPOLL_CTL_DEL]删除的;//总结://EPOLL_CTL_ADD:等价于往红黑树中增加节点//EPOLL_CTL_DEL:等价于从红黑树中删除节点//EPOLL_CTL_MOD:等价于修改已有的红黑树的节点
返回值:
成功:0;失败:-1,设置相应的errno
epoll_wait 等待所监控文件描述符上有事件的产生,类似于select()调用。
函数原型
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
参数
events: 用来存内核得到事件的集合,可简单看作数组。是传出参数,传出满足监听条件的那个fd结构体
maxevents: 告之内核这个events有多大,也就是说,这次epoll_wait 可以收集到的events的个数,这个maxevents的值不能大于创建epoll_create()时的size,
timeout: 是超时时间
-1: 阻塞
0: 立即返回,非阻塞
>0: 指定毫秒
返回值:
成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1
//说白了就是遍历这个双向链表,把这个双向链表里边的节点数据拷贝出去,拷贝完毕的就从双向链表里移除;
//因为双向链表里记录的是所有有数据/有事件的socket【TCP连接】;
//参数epfd:是epoll_create()返回的epoll对象描述符;
//参数events:是内存,也是数组,长度 是maxevents,表示此次epoll_wait调用可以收集到的maxevents个已经就绪【已经准备好的】的读写事件;
//说白了,就是返回的是 实际 发生事件的tcp连接数目;
//参数timeout:阻塞等待的时长;