I/O多路转接之epoll

来源:互联网 发布:java数组排序函数 编辑:程序博客网 时间:2024/05/16 14:27


                             

一、什么是epoll

    epoll是什么?按照man⼿手册的说法:是为处理⼤大批量句柄⽽而作了改进的poll。当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new API introduced in Linux kernel2.5.44),它⼏几乎具备了之前所说的⼀一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知⽅方法。

epoll的相关系统调⽤用

epoll只有epoll_create,epoll_ctl,epoll_wait 3个系统调⽤用。

1. int epoll_create(int size);

创建⼀一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的。需要注意的是,当创建好epoll句柄后,它就是会占⽤用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使⽤用完epoll后,必须调⽤用close()关闭,否则可能导致fd被耗尽。

2. int epoll_ctl(int epfd, int op, int fd, struct 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队列里

3. int epoll_wait(int 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同样只告知那些就绪的文件描述符,⽽且当我们调用epoll_wait()获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定的⼀个数组中依次取得相应数量的文件描述符即可,这里也使用了内存映射(mmap)技术,这样便彻底省掉了这些文件描述符在系统调⽤用时复制的开销。另一个本质的改进在于epoll采⽤用基于事件的就绪通知⽅式。在select/poll中,进程只有在调⽤一定的方法后,内核才对所有监视的⽂文件描述符进⾏行扫描,而epoll事先通过epoll_ctl()来注册⼀个⽂件描述符,⼀旦基于某个⽂件描述符就绪时,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调⽤用epoll_wait()时便得到通知。

Epoll2种工作⽅方式-⽔水平触发(LT)和边缘触发(ET

Edge Triggered工作模式:

如果我们在第1步将RFD添加到epoll描述符的时候使⽤用了EPOLLET标志,那么在第5步调⽤epoll_wait(2)之后将有可能会挂起,因为剩余的数据还存在于⽂文件的输⼊入缓冲区内,而且数据发出端还在等待一个针对已经发出数据的反馈信息。只有在监视的文件句柄上发⽣生了某个事件的时候ET⼯作模式才会汇报事件。因此在第5步的时候,调⽤用者可能会放弃等待仍在存在于⽂文件输⼊入缓冲区内的剩余数据。在上⾯面的例⼦中,会有⼀个事件产⽣在RFD句柄上,因为在第2步执⾏行了⼀个写操作,然后,事件将会在第3步被销毁因为第4步的读取操作没有读空⽂件输⼊入缓冲区内的数据,因此我们在第5步调⽤用epoll_wait(2)完成后,是否挂起是不确定的。epoll⼯工作在ET模式的时候,必须使⽤用⾮非阻塞套接⼜⼝口,以避免由于⼀一个⽂件句柄的阻塞读/阻塞写操作把处理多个⽂件描述符的任务饿死。最好以下⾯面的方式调⽤用ET模式的epoll接口,在后⾯面会介绍避免可能的缺陷。只有当read(2)或者write(2)返回EAGAIN时才需要挂起,等待。但这并不是说每次read()

时都需要循环读,直到读到产⽣⼀个EAGAIN才认为此次事件处理完成,当read()返回的读

到的数据长度小于请求的数据长度时,就可以确定此时缓冲中已没有数据了,也就可以认

为此事读事件已处理完成。

Level Triggered⼯工作模式

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

LT(level triggered)epoll缺省的⼯作⽅方式,并且同时⽀支持blockno-block socket.在这种做法中,内核告诉你⼀个⽂文件描述符是否就绪了,然后你可以对这个就绪的fd进⾏IO操作。如果你不作任何操作,内核还是会继续通知你 的,所以,这种模式编程出错误可能性要⼩小⼀点。

传统的select/poll都是这种模型的代表.

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

epoll_server实现源码:

 

#include<stdio.h>

#include<sys/epoll.h>

#include<sys/socket.h>

#include<sys/types.h>

#include<netinet/in.h>

#include<stdlib.h>

#include<string.h>

static void usage(const char *proc)

{

printf("Usage:%s [local_ip] [local_port]\n",proc);

}

typedef struct fd_buf{

     int fd;

 char buf[10240];

}fd_buf_t,*fd_buf_p;

static void *alloc_fd_buf(int fd)

{

fd_buf_p tem = (fd_buf_p)malloc(sizeof(fd_buf_t));

if(!tem){

   perror("malloc");

   return NULL;

}

tem->fd=fd;

return tem;

}

int startup(const char *_ip,int _port)

{

int sock = socket(AF_INET,SOCK_STREAM,0);

if(sock<0){

perror("socket");

exit(2);

}

int opt = 1;

setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

struct sockaddr_in local;

local.sin_family = AF_INET;

local.sin_port = htons(_port);

local.sin_addr.s_addr = inet_addr(_ip);

if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)

{

perror("bind");

exit(3);

}

if(listen(sock,10)<0)

{

perror("listen");

exit(4);

}

return sock;

}

int main(int argc,char*argv[])

{

if(argc != 3)

{

usage(argv[0]);

return 1;

}

int listen_sock = startup(argv[1],atoi(argv[2]));

int epollfd = epoll_create(256);

if(epollfd<0)

{

perror("epoll_create");

close(listen_sock);

return 5;

}

struct epoll_event ev;

ev.events = EPOLLIN;

ev.data.ptr = alloc_fd_buf(listen_sock);

epoll_ctl(epollfd,EPOLL_CTL_ADD,listen_sock,&ev);

int nums = 0;

    struct epoll_event evs[64];

int timeout = -1;

while(1)

{

switch((nums = epoll_wait(epollfd,evs,64,timeout))){

case -1:

perror("epoll_wait");

break;

case 0:

perror("timeout...!\n");

break;

default:

{

int i = 0;

for(; i<nums; i++){

fd_buf_p fp = (fd_buf_p)evs[i].data.ptr;

if(fp->fd == listen_sock &&\

(evs[i].events & EPOLLIN)){

                        struct sockaddr_in client;

socklen_t len = sizeof(client);

int new_sock = accept(listen_sock,\

(struct sockaddr*)&client,&len);

if(new_sock<0)

{

perror("accept");

continue;

}

printf("get a new client!\n");

ev.events = EPOLLIN;

ev.data.ptr = alloc_fd_buf(new_sock);

epoll_ctl(epollfd,EPOLL_CTL_ADD,\

new_sock,&ev);

}

else if(fp->fd != listen_sock)

{

if(evs[i].events & EPOLLIN)

{

ssize_t s = read(fp->fd, fp->buf,\

sizeof(fp->buf));

if(s > 0)

{

fp ->buf[s]=0;

printf("client say:%s\n",fp->buf);

ev.events = EPOLLOUT;

ev.data.ptr=fp;

epoll_ctl(epollfd,EPOLL_CTL_MOD,\

fp->fd,NULL);

}

else if(s<=0)

{

close(fp->fd);

                            epoll_ctl(epollfd,EPOLL_CTL_DEL,\

fp->fd, NULL);

free(fp);

 

}else{}

}

else if(evs[i].events & EPOLLIN)

{

const char *msg = "H TTP/1.0 200 OK\r\n\r\n<html><h1>hello epoll!</h1></html>";

write(fp->fd,msg,strlen(msg));

close(fp->fd);

epoll_ctl(epollfd,EPOLL_CTL_DEL,\

fp->fd,NULL);

free(fp);

}

else{}

}

else{}

}

}

    break;

}

}

 return 0;

}

 

原创粉丝点击