Linux复用I/O-epoll-server代码

来源:互联网 发布:怎么用sql语句降序排列 编辑:程序博客网 时间:2024/06/05 18:55

参考博客:http://www.cnblogs.com/Anker/p/3265058.htmlselect-poll-epoll

select的几大缺点:

(1)每次调用select,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大

(2)同时每次调用select都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大

(3)select支持的文件描述符数量太小了,默认是1024

select利用fd_set集合,通过轮询的方式,获得有事件触发的socket,而epoll,利用回调函数,将发生事件的就绪fd,加入到就绪列表。

epoll

  epoll既然是对select和poll的改进,就应该能避免上述的三个缺点。那epoll都是怎么解决的呢?在此之前,我们先看一下epoll和select和poll的调用接口上的不同,select和poll都只提供了一个函数——select或者poll函数。而epoll提供了三个函数,epoll_create,epoll_ctl和epoll_wait,epoll_create是创建一个epoll句柄;epoll_ctl是注册要监听的事件类型;epoll_wait则是等待事件的产生。

  对于第一个缺点,epoll的解决方案在epoll_ctl函数中。每次注册新的事件到epoll句柄中时(在epoll_ctl中指定EPOLL_CTL_ADD),会把所有的fd拷贝进内核,而不是在epoll_wait的时候重复拷贝。epoll保证了每个fd在整个过程中只会拷贝一次

  对于第二个缺点,epoll的解决方案不像select或poll一样每次都把current轮流加入fd对应的设备等待队列中,而只在epoll_ctl时把current挂一遍(这一遍必不可少)并为每个fd指定一个回调函数,当设备就绪,唤醒等待队列上的等待者时,就会调用这个回调函数,而这个回调函数会把就绪的fd加入一个就绪链表)。epoll_wait的工作实际上就是在这个就绪链表中查看有没有就绪的fd(利用schedule_timeout()实现睡一会,判断一会的效果,和select实现中的第7步是类似的)。

  对于第三个缺点,epoll没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大

总结:

(1)select,poll实现需要自己不断轮询所有fd集合,直到设备就绪,期间可能要睡眠和唤醒多次交替。而epoll其实也需要调用epoll_wait不断轮询就绪链表,期间也可能多次睡眠和唤醒交替,但是它是设备就绪时,调用回调函数,把就绪fd放入就绪链表中,并唤醒在epoll_wait中进入睡眠的进程。虽然都要睡眠和交替,但是select和poll在“醒着”的时候要遍历整个fd集合,而epoll在“醒着”的时候只要判断一下就绪链表是否为空就行了,这节省了大量的CPU时间。这就是回调机制带来的性能提升。

(2)select,poll每次调用都要把fd集合从用户态往内核态拷贝一次,并且要把current往设备等待队列中挂一次,而epoll只要一次拷贝,而且把current往等待队列上挂也只挂一次(在epoll_wait的开始,注意这里的等待队列并不是设备等待队列,只是一个epoll内部定义的等待队列)。这也能节省不少的开销。

epoll
epollLinux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。目前epelllinux大规模并发网络程序中的热门首选模型。
epoll除了提供select/ poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。水平触发,数据没有接收完就一直通知,而边沿触发只通知一次,水平触发确保数据的完整性,而边沿触发减少系统调度,提高应用程序的效率。


一个进程打开大数目的socket描述符
cat /proc/sys/fs/file-max
设置最大打开文件描述符限制
最大打开文件个数设置
sudo vi /etc/security/limits.conf
写入以下配置,soft软限制,hard硬限制
* soft nofile 65536
* hard nofile 100000


epoll API


1.创建一个epoll句柄,参数size用来告诉内核监听的文件描述符个数,跟内存大小有关
int epoll_create(int size)
size:告诉内核监听的数目

2.控制某个epoll监控的文件描述符上的事件:注册、修改、删除。

#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfdepoll_creat的句柄
op表示动作,用3个宏来表示:
EPOLL_CTL_ADD(注册新的fdepfd)
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;

Uint32_t u32;

Uint64_t u64;

}epoll_data_t;
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT:表示对应的文件描述符可以写
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来)
EPOLLERR:表示对应的文件描述符发生错误
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLETEPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里


3.等待所监控文件描述符上有事件的产生,类似于select()调用。
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
events:用来从内核得到事件的集合,
maxevents:告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size
timeout:是超时时间
-1:阻塞
0:立即返回,非阻塞
>0:指定微秒
返回值:成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1

======================epoll-server================================

#include <stdio.h>#include <unistd.h>#include <string.h>#include <stdlib.h>#include <ctype.h>#include <sys/types.h>#include <sys/stat.h>#include <sys/socket.h>#include <arpa/inet.h>#include <netinet/in.h>#include <sys/epoll.h>#defineEPOLL60000#define BUF1500#defineLISTEN128#definePORT8000int main(void){int ready,listenfd,clientfd,epfd,i,j,len,client[EPOLL];struct sockaddr_in serveraddr, clientaddr;struct epoll_event tmp,ep[EPOLL];char buf[BUF];socklen_t client_len;listenfd = socket(AF_INET,SOCK_STREAM,0);bzero(&serveraddr,sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);serveraddr.sin_port=htons(PORT);bind(listenfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));listen(listenfd,LISTEN);for(i=0;i<EPOLL;i++)client[i]=-1;epfd = epoll_create(EPOLL);/*set listen 读事件*/tmp.events=EPOLLIN;tmp.data.fd = listenfd;/*根据listenfd插入一个监听节点,准备data.fd以待传出*/epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&tmp);while(1){ready = epoll_wait(epfd,ep,EPOLL,-1);for(i=0;i<ready;i++){if(ep[i].data.fd == listenfd){//处理listenfd的数据client_len = sizeof(clientaddr);clientfd = accept(listenfd,(struct sockaddr*)&clientaddr,&client_len);for(j=0;j<EPOLL;j++){if(client[i]==-1){client[i]=clientfd;break;}}tmp.events = EPOLLIN;tmp.data.fd = clientfd;epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&tmp);}else{//处理普通socket接收的数据 len = read(ep[i].data.fd,buf,sizeof(buf));if(len == 0){epoll_ctl(epfd,EPOLL_CTL_DEL,ep[i].data.fd,NULL);close(ep[i].data.fd);}else if(len < 0){perror("read client error\n");exit(1);}else{int n = 0;while(n < len){buf[n]=toupper(buf[n]);n++;}write(ep[i].data.fd,buf,len);}}}}close(listenfd);}


原创粉丝点击