一、epoll()接口解析

        1.定义

          epoll是Linux内核为处理大批句柄而作改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著的减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。因为它会复用文件描述符集合来传递结果而不是迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一个原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select\poll那种IO事件的电平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提供应用程序的效率。

           2.工作方式:

      LT(level triggered):水平触发,缺省方式,同时支持block和no-block socket,在这种做法中,内核告诉我们一个文件描述符是否被就绪了,如果就绪了,你就可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错的可能性较小。传统的select\poll都是这种模型的代表。

      ET(edge-triggered)边沿触发,高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪状态时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如:你在发送、接受或者接受请求,或者发送接受的数据少于一定量时导致了一个EWOULDBLOCK错误)。但是请注意,如果一直不对这个fs做IO操作(从而导致它再次变成未就绪状态),内核不会发送更多的通知。

      区别:LT事件不会丢弃,而是只要读buffer里面有数据可以让用户读取,则不断的通知你。而ET则只在事件发生之时通知。

        3.参数及对应接口

        1、int epoll_create(int size)

            创建一个epoll句柄,参数size(现在已不用,只在创建时)用来告诉内核监听的数目。

            注意:size这个参数不同于select()中的第一个参数,给出最大监听的fd+1的值。需要注意的是,当创建好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事件注册函数,

       参数epfd  为epoll的句柄(即 epoll_create创建的epoll专用文件描述符);

       参数op表示动作,用3个宏来表示:

                EPOLL_CTL_ADD(注册新的fd到epfd),                        

                EPOLL_CTL_MOD(修改已经注册的fd的监听事件),

                EPOLL_CTL_DEL(从epfd删除一个fd);

       参数fd为需要监听的标示符;

       参数event告诉内核需要监听的事件,event的结构如下:

            typedef union epoll_data {
                    void *ptr;
                    int fd;
                     __uint32_t u32;
                     __uint64_t u64;
            } epoll_data_t;

            struct epoll_event {
                 __uint32_t events;  /* Epoll events */
                 epoll_data_t data;  /* User data variable */

            };

      其中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)

          等待事件的产生,类似于select()调用。参数events用来从内核得到事件的集合,maxevents告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时。


二、epoll()的使用框架

        应用环境:tcp服务器下,在socket 监听 套接字(listen_sock) 已经成功bind ,并设为listen 状态。

        1.int epoll_fd=epoll_create(size);

                调用epoll_create生成 epoll专用字柄。

          2.声明 struct epoll_event ev 与 struct epoll_event revent[ MAX_FD]

声明timeout

             ev用来添加 内核监听 不同状态的句柄 的相关初始化(可以复用);

             revent 用来储存 每次监听完毕返回到用户态时,保存每个监听句柄的状态(通过 revents[i].revents 的值与 上边 介绍的宏 比较 确定状态)

                timeout 用来确定 内核最长执行一次监听的时间

          3.ev.data.fd=listen_sock  ev.events=EPOLLIN|EPOLLET;

             将监听套接字 与 关心的事件 先存入 ev对应 域

          4.调epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_sock,&ev),将ev 中的内容添加到 监听队列

         5.开始监听(用分支语句 判断 返回结果 switch 或 if—else)

           swith (ret=epoll_wait(epoll_fd,event,256,timeout)){

                case -1: 出错    break;

                case    0:  超时   break;

                default :  执行相关操作  break;

           }

            注意:因为我们为了效率最高,因此将 监听模式  设为 ET,所以每次添加每个套接字 文件描述符 之前 ,先要将 其操作模式设为 非阻塞模式; 

            

            通过fcntl设置文件描述符属性

            fcntl即F_SETFL,F_GETFL的使用,设置文件的flags,阻塞设置成非阻塞,非阻塞设置成阻塞(这连个在server开发中可以封装为基本函数)  

            1、获取文件的flags,即open函数的第二个参数:

               flags = fcntl(fd,F_GETFL,0);

            2、设置文件的flags:

                fcntl(fd,F_SETFL,flags);

            3、增加文件的某个flags,比如文件是阻塞的,想设置成非阻塞:

            flags = fcntl(fd,F_GETFL,0);       flags |= O_NONBLOCK;

三、使用epoll()的TCP  服务端 实例

#include <stdio.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>

#define _MAXFD_ 10 

typedef struct fd_buf{
	int fd;
	char *buf;
}_buf,*ptr_buf;

int set_non_block(int fd)
{
	int old_flag=fcntl(fd,F_GETFL);
	if(old_flag<0){
		perror("fcntl");
		exit(-4);
	}
	if(fcntl(fd,F_SETFL,old_flag|O_NONBLOCK)<0){
		perror("fcntl");
		return -1;
	}

	return 0;

}

int write_fd(ptr_buf fd_buf,int num,int epoll_fd,struct epoll_event* event)
{
		int fd=fd_buf->fd;
		ssize_t size=0;

		while(1){
			size=write(fd,fd_buf->buf,strlen(fd_buf->buf));
			if(size<0&&errno!=EAGAIN){	
				perror("write");
				return -1;
    	 	}
			if(errno==EAGAIN){
		//	printf("no memory to write!\n");
			return -2;
			}
		}
		return fd;

}

ptr_buf read_fd(int epoll_fd,int fd,ptr_buf buf,struct epoll_event* event,int num)
{	
	if(	set_non_block(fd)<0){
		
		return NULL;
	}
	
	buf->fd=fd;
	buf->buf=(char*)malloc(sizeof(char)*1024);
	ssize_t size;
	int _num=0;
		
	size=read(fd,buf->buf+_num,1024);
//	printf("size %d\n",size);
	if(size<0){
		perror("read");
		return NULL;
	}
	if(size==0){
		struct epoll_event ev;
		ev.data.fd=fd;
		close(fd);
		epoll_ctl(epoll_fd,EPOLL_CTL_DEL,fd,event);
		printf("client [%d] close...\n",fd);
		free(buf->buf);
		free(buf);
		return NULL;
	}
	_num+=size;
	while(size=read(fd,buf->buf+_num,1024)>0){
		if(errno==EAGAIN){
			break;
		}
		_num+=size;
	}
	if(_num>1023){
		printf("client [%d]'s buf overflow!...",fd);
		buf->buf[1023]=0;
	}
	else
		buf->buf[_num]=0;
	printf("client [%d]# %s\n",fd,buf->buf);
	return buf;
}

int startup(char* ip,int port)
{
	int sock=socket(AF_INET,SOCK_STREAM,0);
	struct sockaddr_in server;
	server.sin_family=AF_INET;
	server.sin_addr.s_addr=inet_addr(ip);
	server.sin_port=htons(port);
//	printf("port  %d  %d",port,(htons(port)));
	if(bind(sock,(struct sockaddr *)&server,sizeof(server))<0){
		perror("bind");
		exit(-2);
	}
	if(listen(sock,5)<0){
		perror("listen");
		exit(-3);
	}
	return sock;
}

void usage(char* arg)
{
	printf("usage %s [ip] [port]\n",arg);

}

void epollup(int sock)
{	
	int epoll_fd=epoll_create(256);
	if(epoll_fd<0){
		perror("epoll");
		return;
	}
	int timeout_num=0;
	int done=0;
	int timeout=5000;
	int i=0;
	int ret_num=-1;

	struct epoll_event ev;
	struct epoll_event event[10];
	ev.data.fd=sock;
	ev.events=EPOLLIN|EPOLLET;
//	fd_num=1;
//	printf("listen sock%d\n",sock);
	if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,sock,&ev)<0){
		perror("epoll_ctl");
		return ;
	}
	while(!done){
		switch(ret_num=epoll_wait(epoll_fd,event,256,timeout)){
		case -1:{
			perror("epoll_wait");	
			break;
		}
		case 0 :{
			if(	timeout_num++>5)
				done=1;
			printf("time out...\n");
			break;
		}
		default:{
				for(i=0;i<ret_num;++i){
					if(event[i].data.fd==sock&&event[i].events&EPOLLIN){
						int new_sock=-1;
						struct sockaddr_in client;
						socklen_t len=sizeof(client);
						if((new_sock=accept(sock,(struct sockaddr*)&client,&len))<0){
							perror("accept");
							printf("%s : %d \n",strerror(errno),new_sock);
							continue;
						}
						//printf("%s\n",strerror(errno));			
	//					printf(" epoll accept  %d\n",new_sock);
						ev.data.fd=new_sock;
						ev.events=EPOLLIN|EPOLLET;
						if(epoll_ctl(epoll_fd,EPOLL_CTL_ADD,new_sock,&ev)<0){
							perror("epoll_ctl");
							return ;
						}
//						fd_num++;
						printf("get a connect [%d]...\n",new_sock);
					}
					else {
						if(event[i].events&EPOLLIN){
	//						printf("event read!\n");
							int fd=event[i].data.fd;
							ptr_buf buf=(ptr_buf)malloc(sizeof(_buf));
							if(read_fd(epoll_fd,fd,buf,event,i)==NULL)
								continue;
							event[i].data.ptr=buf;
							event[i].events=EPOLLOUT;
						}
						if(event[i].events&EPOLLOUT){
							int fd=-1;
							if(fd=write_fd(event[i].data.ptr,i,epoll_fd,event)<0){
								continue;
							}
							event[i].data.fd=fd;
							event[i].events=EPOLLIN|EPOLLET;
						}
						
					}
				}
			break;
		}
		
		}
	}
}

int main(int argc,char* argv[]){
	if(argc!=3){
		usage(argv[0]);
		exit(-1);
	}	
	int port=atoi(argv[2]);
//	printf("port %s %d",argv[2],port);
	
	int listen_sock=startup( argv[1],port);
 //   set_non_block(listen_sock);
	epollup(listen_sock);
	close(listen_sock);
	return 0;
}