I/O复用——epoll函数

          select函数有效地解决了多个I/O端口的复用问题,但是select函数存在两个缺陷:

一是进程所能同时打开的文件描述符个数受FD_SETSIZE大小的限制;

二是每个select函数返回可用的文件描述符集合后,应用都必须对所有已注册的文件描述符进行遍历对比,以确定哪个描述符上发生了事件,从而对其进行读写操作。

         于是随着文件描述符的增加,系统的性能线性下降,从Linux内核2.6开始,提供了一种新的I/O模型,称为事件I/O(epoll)。epoll有效解决了select存在的问题,称为Linux平台上逐渐流行的编程模式。

二、关于epoll函数的3个系统调用

1、epoll_create  

#include<sys/epoll.h> 

int epoll_create(int size) 

返回值:文件描述符表示成功; -1表示错误; errno记录错误号

epoll_create()函数调用成功,则初始化一个epoll实例,并返回一个和此实例相关联的文件描述符,该文件描述符实际并不指向任何真实的文件,仅作为句柄用于后续使用此epoll实例,size参数表示应用预计需要内核监视的文件描述符个数,注意该参数并不表示最大监视文件描述符个数,而只是一个告诉内核的提示数目,内核可以根据此size参数分配合适的内部数据结构,因此该数据越准确,就越能获得更好的性能。当发生错误时,epoll_create返回-1,并且设置错误代码errno为以下几个值:

a.  EINVAL  参数size不是一个整数

b.  ENFILE  系统已经分配了最大限度的文件描述符个数了

c.  ENOMEM 无足够内存完成此操作

2、epoll_ctl 

#include<sys/epoll.h> 

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event); 

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

epfd是epoll_create()的返回值;op表示动作,用三个宏来表示:EPOLL_CTL_ADD:注册新的fd到epfd中;EPOLL_CTL_MOD:修改已经注册的fd的监听事件;EPOLL_CTL_DEL:从epfd中删除一个fd;

fd是需要监听的fd;

event是告诉内核需要监听什么事,struct epoll_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、epoll_wait     

#include<sys/epoll.h> 

int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout); 

返回值:准备好的文件描述符个数表示成功,-1表示错误,errno记录错误号

调用epoll_waith后,进程将等待事件的发生,直到timeout参数设定的超时值到时为止,当成功返回后,则返回值为发生了所监视事件的文件描述符个数,并且参数events指向被返回的事件,本次epoll_wait最多可以返回maxevents个事件,因此可以通过遍历的方式逐个处理发生了事件的哪些文件描述符,另外,如果epoll_wait返回了maxevents个事件,并不表示当前只有maxevents个事件发生,而只是说本次调用能处理的事件个数为maxevents,剩下已产生但是未处理的事件存放到epoll上下文的事件队列中,因此将在下次调用epoll_wait并返回时进行处理,epoll_wait调用失败返回-1,并设置错误码如下值:

a.  EBADF   epfd为一个非法文件描述符

b.  EFAULT  进程没有events参数所指向内存的写权限

c.  EINTR   epoll_wait系统调用被信号中断

d.  EINVAL  epfd不是合法epoll文件描述符,或者maxevents小于或者等于0

注意:若参数timeout为0,则epoll_wait立即返回,即使没有任何事件发生,并且返回值为0,若参数为-1,则不返回直到发生事件为止。

epoll.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <assert.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#define MAXFD 10
 
void epoll_add(int epfd,int fd)//往内核事件表中添加文件描述符
{
	struct epoll_event ev;
	ev.events = EPOLLIN;//注册读事件,设置关注
    ev.data.fd = fd;
    if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev )== -1)  
    //EPOLL_CTL_ADD为操作方法添加
    {  
        perror("epoll_ctl add error");  
	}
    
}
void epoll_del(int epfd,int fd)//从内核事件表中移除文件描述符
{
	if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL) == -1 )
    //EPOLL_CTL_ADD为操作方法移除
	{
		perror("epoll_ctl del error");
	}
}
 
int main()   
{   
	int sockfd = socket(AF_INET,SOCK_STREAM,0);
	assert(sockfd != -1);
 
	struct sockaddr_in saddr,caddr;
	memset(&saddr,0,sizeof(saddr));
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
 
	int res = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
	assert(res != -1);
 
	listen(sockfd,5);
	assert(sockfd != -1);
 
    int epfd = epoll_create(MAXFD);  
    //系统调用,在内核空间中创建内核事件表,实际为一种红黑树的数据结构
    if(epfd == -1)  
    {  
        perror("epoll_create failed");  
   
    }  
     
	epoll_add(epfd,sockfd);//把事件和文件描述符添加到红黑树中的结点中去
    struct epoll_event events[MAXFD]; 
    //因为epoll_wait()会把就绪的文件描述符存在一个数组中,所以定义这个数组
 
    while(1)  
    {  
        int n = epoll_wait(epfd,events,MAXFD,5000);  
        //检查就绪并且将其添加到events这个数组中
        if(n == -1)  //失败
        {  
            perror("epoll_wait error");  
            continue;
        }  
		else  if(n == 0)//超时
		{
			printf("time out\n");
			continue;
		}
        //有n个数据元素就绪
		else
		{
			int i = 0;  
			for(;i < n;i++)  
			{  
				int fd = events[i].data.fd;
			    if(events[i].events & POLLIN)
                //因为有多种事件,因此要检查是哪种类型的时间,在此关注检查读事件
                {
					if(fd == sockfd)  //监听套接字
					{  
						int len = sizeof(caddr);	
 
						int c = accept(sockfd,&caddr,&len);
						if(c <= 0)
						{
							continue;
						}
						
						printf("accept c=%d\n",c); 
           
						epoll_add(epfd,c);  //新的连接产生,添加到内核事件表中
				
					}
        
				    else  
				    {  
					  char buff[128] = {0};
					  int num = recv(fd,buff,127,0)
					  if( num <= 0)  //对方关闭
					  {  
                        //先epoll_del()移除再close()关闭,因为epoll_del()中epoll_ctl()这
                        //个系统调用要用到fd,所以不能先关闭,如果先关闭的话,就找不到了,在执
                        //行时会提示为无效描述符close()不会使值发生变化,但会使得值无效
						epoll_del(epfd,fd);
						close(fd);
						printf("one client close\n");
                        continue;
					  }  
					  printf("recv(%d):%s\n",fd,buff);  
					  send(fd,"OK",2,0);  
				   }
               }  
			}  
		}
	}  
}  
 

运行结果:

猜你喜欢

转载自blog.csdn.net/Aspiration_1314/article/details/83420850