Chinaunix首页 | 论坛 | 博客
  • 博客访问: 181951
  • 博文数量: 64
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 616
  • 用 户 组: 普通用户
  • 注册时间: 2015-06-09 20:25
文章分类

全部博文(64)

文章存档

2016年(25)

2015年(39)

我的朋友

分类: LINUX

2015-09-02 16:29:12

看了一篇博客,写得太好了,自己想写点,结果按照人家的打了一遍。。。。资源共享下

Epoll是为了处理大批量 句柄而作了改进的poll,是linux2.6下性能最好的多路I/O就绪通知方法。他的作用是显著较少进程在大量并发连接中只有少量活跃的情况下的系统CPU利用率。

Epoll的优点有:

1.支持一个进程打开大数目的socket描述符

2.IO效率不随FD数目增加而现行下降

3.内核微调

Epoll为什么会高效?
当每一个进程调用epoll_create方法的时候,linux内核会创建一个eventpoll结构体,这个结构体中有两个成员和epoll的使用方式很相关。

每一个epoll对象都有一个独立的eventpoll结构体,用于存放通过epoll_ctl方法向epoll对象中添加进来的事件,这些事件都会挂载在红黑树上,重复的事件红黑树能搞笑的识别出来,红黑树的插入事件效率是lgn。所有添加到epoll中的事件都会与设备(网卡)驱动程序简历回调关系,当相应的事件发生时会调用这个回调方法。这个回调方法在内核中是ep_poll_callback,它会将发生的事情加载在rdlist双链表中。在epoll中每一个事件,都会建立一个epitem结构体。
当调用epoll_wait检查是否事件发生时,只需要检查eventpoll对象中的edlist双链表中是否有epitem元素就可以,如果rdlist不为空,则把发生的事件复制到用用户态,同事将事件数量返回给用户。

Linux下设计并发网络程序的典型方法有Apache模型(Process Per Connection PPC),TPCThread Per Connection)模型,select模型和poll模型

其中PPCTPC模型是让每一个到来的连接干自己的事情,因为PPC是为它开一个进程,而TPC开一个线程。但是多进程,多线程的切换,会导致开销的增加,所以这类模型的连接数都比较低,几百。

Select模型,最大并发限制数,因为一个进程锁打开的FD是有限的,由FD_SETSIZE设置,一般为1024/2048。虽然可以改,但是效率会降低,select每次调用都会现行扫描全部的FD集合,所以把FD_SETSIZE改大后会超时。内核/用户空间传递消息。Select采用内存拷贝机制。

Poll模型

Epoll模型,epoll模型没有设置最大的并发数,和内存大小相关,虽然这样,epoll只在乎活跃的连接,总的连接无关。而在内存拷贝上epoll使用的是共享内存,所以无需拷贝。Epoll不仅会告诉应用程序有I/O事件的到来,还会告诉应用程序相关的信息。这些信息是应用程序填充的。根据这些信息,应用程序就能直接定位到事件,不必访问整个FD集合。

Epoll是基于事件触发的,比select快的多,单线程epoll,触发量可达到15000,加上业务后,大多数业务都与数据库打交道,会存在阻塞现象,必须用多线程提速。

 

Epoll在线程池内,数量为2000/s增加网络断线后的无效socket检测。

Epoll的相关系统调用有epoll_createepoll_ctlepoll_wait三个、

Int epoll_create(int size):创建一个epoll的句柄,linux2.6.8以后,size参数被忽略,当创建好epoll句柄后,他就会占用一个fd值,在linux下查看/proc/进程id/fd/,可以查看到这个fd,所以在使用完epoll后必须调用close()关闭,否则会导致fd被耗尽。

Int epoll_ctlint epfdint opint fdstruct epoll_event *event);

Epoll是事件注册函数,它不同于select()是在坚挺事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。

第一个参数是epoll_create()的返回值。

第二个参数表示动作,用三个宏来表示。

EPOLL_CTL_ADD:注册新的fdepfd中。

EPOLL_CTL_MOD: 修改已经注册的fd的监听事件。

EPOLL_CTL_DEL: 从epfd中删除一个fd

第三个参数是需要监听的fd

第四个参数是告诉内核需要监听什么事件。

Struct epoll_event结构体如下:


Events 可以是一下几个宏的集合:

EPOLLIN:表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

EPOLLOUT:表示对应的文件描述符可以写;

EPOLLPRI:表示对应的文件描述符有紧急的数据刻度(这里表示有外带数据的到来);

EPOLLERR:表示对应的文件描述符发生错误;

EPOLLHUP: 表示对应的文件描述符被挂断;

EPOLLET:将EPOLL设为边缘触发(Edge Triggered)模式,相对于水平触发(Level Triggered)。

EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续坚挺这个socket的话,需要再次把这个socket加入到EPOLL队列里。

Int epoll_waitint epfd,struct epoll_event* events,int maxevents,int timeout;

收集在epoll监控的事件中已经发送的事件,参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据赋值到这个events数组中,不回去帮助我们在用户态中分配内存)。Maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定或者永久阻塞)。如果函数调用成功,返回对应I/O上已经准备好的文件描述符数目,返回0则表示已超时。

 

Epoll只告知那些就绪的文件描述符,而且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的一个数组中一次取得相应数量的文件描述符即可,这里使用了内存映射(mmap)技术。这样就省略掉这些文件描述符在系统调用时复制的开销。

另一个就是在epoll采用基于时间的就绪通知方式,在select/poll中,进程只有在调用一定的方法后,内核才对所有监视的文件描述符进行扫描,而epoll实现通过epoll_ctl()来注册一个文件描述符,一旦基于某个文件描述符就绪时,内核会采用类似callback的毁掉机制,迅速激活这个文件描述符,当进程调用epoll_wait()时便得到通知。

 

Epoll的两种工作方式

水平触发(LT)和边缘触发(ET

1.       从管道中读取数据的文件句柄添加到epoll描述符

2.       同时管道的另一端被写入2   KB的数据

3.       调用epoll_wait(2),它会并且返回RFD,即已经准备好读取操作

4.       读取1kb的数据

5.       调用epoll_wait(2)

两者的差别在LT模式下只要某个socket处于readable/writeable状态,无论什么时候进行epoll_wait都会返回该socket;而ET模式下只有某个socketunreadable变为readable或从unwitable变为writable时,epoll_wait才会返回这个socket

 

 

水平触发时,socket可写时,会不停的触发socket可写事件,该怎么办。

1.       socket需要写数据的时候才把socket加入epoll,等待可写事件,接收到可写事件后,调用writesend发送数据。当处理完后,把socket移出epoll。代价高

2.       不把socket加入epoll,需要向socket写数据时,直接调用write或者send发送数据。如果返回EAGAIN,把socket加入epoll,在epoll的驱动下写数据,发送完后,再移出epoll

 

Edge triggered工作模式:

如果我们在第一部将RFD添加到epoll描述符的时候使用了EPOLLET标示,那么在第五步调用epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于文件的输入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发生了某个时间的时候ET工作模式才会汇报时间。所以在第五步的时候,调用者可能会放弃等待仍然存在存于文件输出缓冲区的剩余数据,上例中,会有一个时间产生在RFD句柄上,因为在第二部执行了一个写操作,然后时间会在第三步被销毁,因为第四部的读操作没有读空文件输入缓冲区内的数据,因此我们在第五步调用epoll_wait(2)完成后,是否挂起是不确定的。Epoll工作在ET模式的时候,必须使用非阻塞套接口,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务饿死。最好的办法是调用ET模式的epoll接口。基于非阻塞文件句柄,只有当read2)和write2)返回EAGAIN时才需要挂起,等待,但这并不是说每次read()时都需要循环读,知道督导产生一个EAGAIN才认为此次事件处理完,当read()返回读到的数据长度小于请求的数据长度时,就可以确定此事缓冲中已经没有数据了,也就可以认为此事读事件已处理完成。

Level Triggered工作模式

LT方式调用epoll接口的时候,它相当于一个速度表较快的poll2),并且无论后面的数据是否被使用,因此他们具有同样的只能,因为即使时能ET模式的epoll,在受到多个chunk的数据的时候仍然会产生多个事件。调用者可以设定EPOLLONESHOT标志,在epoll_wait(2)收到事件后epoll会与事件关联的文件句柄从epoll描述符中禁止,因此当EPOLLLONESHOT设定后,使用带EPOLL_CTL_MOD标志的epoll_ctl(2)吃力文件句柄就成为调用者必须作的事情。

LTepoll缺省的工作方式,并且同事支持blockno-block socket。在这种做法中,内核告诉你一个文件描述符是否就绪,然后你可以对这个就绪的fd进行IO操作,如果你不做任何操作,内核还会继续通知你,所以,这种模式变成出错误的可能性非常小,传统的select/poll都是这种典型的模式代表

ET是告诉工作方式,只支持no-block socket,它效率要比LT更高,ETLT的区别在于,当一个新的实践到来时,ET模式下当然可以从epoll_wait调用中获取到这个实践,可是如果这次没有把这个实践对应的套接字缓冲区处理完,在这个套接字中没有新的实践再次到来时,在ET模式下是无法再次从epoll_wait调用中获取这个时间的,而LT模式正好相反,只要一个事件对应的套接字缓冲区还有数据,就总能从epoll_wait中获取这个事件。因此,LT漠视下开发基于epoll的应用要简单些,不太容易出错,而在ET模式下事件发生时,如果没有彻底地将缓冲区数据处理完,则会导致缓冲区中的用户请求的不到响应。

Epoll的优点:

1.       支持一个进程打开大数目的socket描述符(FD

Select规定一个进程锁打开的FD是有一定限制的,由FD_SETSIZE设置的,默认值是2048,需要修改这个宏重新编译内核,但是也带来了网络效率的下降。选择多进程的解决方案,进程间数据同步远比线程间同步的高效。而epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目。这个数字一般远远大于2048.可以通过cat proc/sys/fs/file-max查看。

2.       IO效率不随FD数目增加而线性下降,传统的select/poll当你拥有一个很大的socket集合,由于网络延时,任一事件只有部分的socket是活跃的,但是seletc/poll每次调用都会现行扫描全部的集合,导致效率呈现现行下降,但是epoll不存在这个问题,只会对活跃的socket进行操作,这是因为在内核实现中epoll是根据每个fd上面的callback函数实现的,那么,只有活跃的socket才会主动的去调用callback函数,其他idle状态socket则不会,在这点上,epoll实现了一个伪AIO,因为这时候推动力在os内核,在一些benchmark中,如果所有的socket基本上都是活跃的,比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多食用epoll_ctl,效率相比还有稍微的下降,但是一旦食用idle ocnnections模拟WAN环境,epoll的效率就远在select/poll之上了。

3.       使用mmap加速内核与用户空间的消息传递,这点实际设计到epoll的具体实现了,无论是selectpoll还是epoll都需要把FD消息通知给用户控件,如何避免不必要的内存拷贝就很重要,epoll是通过内核于用户控件mmap同一块内存实现的。

4.       内核微调,内核      TCP/IP协议栈使用内存池管理sk_buff结构,那么可以在运行时期动态调整这个内存poolskb_head_pool)的大小---通过echo XXXX》、proc/sys/net/core/hot_list_length完成。再比如listen函数的第二个参数(TCO完成3次握手的数据包队列长度),也可以根据你平台内存大小动态调整。

Linuxepoll如何实现高效处理百万句柄,开发高性能网络程序时,windowsiocplinux下则是epollepoll是一种IO多路复用技术,可以非常告诉的处理数以百万计的socket句柄,比起以前的selectpoll效率高很多。使用方法是epoll_create建立一个epoll对象。参数size是内核保证能够正确处理的最大句柄数,多于这个最大数时黑河不保证效果。

Epoll_ctl可以操作上面建立的epoll,将刚建立的socket加入到epoll中让其监控,或者把epoll正在监控的某个socket句柄移出epoll,不再监控它

Eepoll-wait在调用时,在给定的timeout时间内,当在监控的所有句柄中有时间发生时,就返回用户态进程。

Epollselect/poll好的地方在于,后者每次调用时都传递给你所要监控的所有socketselect/poll系统调用,这就意味着需要将用户态的socket列表copy到内核态,如果以万计数的句柄就要拷贝几百KB的内存到内核态,效率非常低,而我们调用epoll_wait时就相当于以往调用select/poll,但是这是却不用传递socket句柄给内核,因为内核已经在epoll_ctl中拿到了要监控的句柄列表。所以当调用epoll_create后,内核就已经在内核态开始准备存储要监控的句柄,每次调用epoll_ctl只是在往内核的数据结构里塞入新的socket句柄。

当一个进程调用epoll_creaqte方法,linux内核会创建一个eventpoll结构体,这个结构体中有两个成员于epoll的使用方式相关。

每一个epoll对象都有一个独立的eventpoll结构体,这个结构体会在内核空间中创造独立的内存,用于存储使用epoll_ctl方法向epoll对象中添加进来的事件。这样,重复的事件就可以通过红黑树而高效的识别出来,在epoll中,对于每一个事件都会建立一个epitem结构体。

此外,epoll还维护了一个双链表,用户存储发生的事件,当epoll_wait调用时,仅仅观察这个list链表里有没有数据即eptime项目即可,有数据就返回,没数据就sleep,等到timeout时间到后即使链表没数据也返回,所以,epoll_wait非常高效。而且,通常情况下,即使我们要监控百万级的句柄,大多一次也只返回很少量的准备就绪句柄而已,所以,epoll_wait仅需要从内核态copy少量的句柄到用户态而已。

准备就绪list链表如何维护的。当执行epoll_ctl时候,除了把socket放到epoll文件系统的file对象对应的红黑树上之外,还会给中断处理程序注册一个毁掉函数,告诉内核,如果这个句柄的中断到了,就把它放到准备就绪list链表里。所以,当一个socket上有数据到了,内核在把网卡上的数据copy到内核中后就来把socket插入到准备你就绪链表里。

一颗红黑树,一张准备就绪的句柄链表,少量的内核cache,就可以帮助我们解决大病发下的socket处理问题。执行epoll_create,创建红黑树和就绪链表,执行epoll_ctl,如果增加socket句柄,则检查在红黑树中是否存在,不存在就添加到树干上,然后向内核注册毁掉函数,用于当中断时间来临时向准备就绪链表中插入数据,执行epoll_wait时立刻返回准备就绪链表里的数据。

 

Epoll的使用方法

通过包含一个文件头#inlclude<sys/epoll.h>以及几个见得的API将可以大大提高你的网络服务器的支持人数,首先通过create_epoll(int maxfds)来创建一个epoll的句柄,这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,用close()来关闭这个创建出来的epoll句柄。

之后在网络主循环里,每一帧的调用epoll_waitint epfdepoll_event eventsint max eventsint timeout)来查询所有的网络接口,可以看出哪一个可以读,哪一个可以写。

Nfds=epoll_wait(kdpfd,events,maxevents,-1)

其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件,max_events是当前需要监听的所有socket句柄数,最后一个timeoutepoll_wait的超时,为0的表示马上返回,为-1的表示一直等下去,知道事件返回,为任一正整数的时候表示等这么长的事件,如果一直没有事件,则返回,一般如果网络主循环是单独的县城的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个县城的话,则可以用0来保证主循环的效率。

Epoll_wait返回之后应该是一个循环,遍历所有的事件。

一般的模板是

 

 

 

 

 

参考自http://blog.csdn.net/xiajun07061225/article/details/9250579,万分感谢提供这么详细的资料

阅读(886) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~