I/O复用—epoll


由于select和poll 存在很多不足之处,因此我们针对这些缺点使用epoll解决。

epoll系统调用

内核事件表

epoll是Linux特有的I/O复用函数。它用一组函数来完成任务,而不是一个函数

epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中(只需传一次),不是像select和poll那样每次循环都要将事件集传给内核。

唯一标识内核中的事件表,用epoll_create函数来创建
epoll_create函数

#include<sys/epoll.h>
int epoll_create(int size)

size:不起作用,只是给内核一个提示,告诉它事件表需要多大。函数的返回值用来指定要访问的内核事件表。

操作epoll的内核事件表的函数

epoll_ctl()函数

#include<sys/epoll.h>
int epoll_ctl(int epfd,int op,int fd,struct_event *event)
  • fd:指定要操作的文件描述符
  • op:指定操作类型

在这里插入图片描述

  • event:指定事件,是epoll_event结构指针类型
struct  epoll_event
{
	_uint32_t  events;  //epoll事件
	epoll_data_t  data;   //用户数据
}
  1. events:成员描述事件类型,于poll支持的事件类型基本相同。就是在POLL对应的宏前加上“E”,例如:数据可读事件(EPOLLIN),同时epoll由两个额外的事件类型:EPOLLET、EPOLLONESHOT
  2. data:存储用户数据
typedef  union  epoll_data
{
	void *ptr;
	int fd;
	uint32_t  u32;
	uint64_t  u64;
}epoll_data_t;

fd:指定事件所从属的目标文件描述符。
ptr:指定与fd相关的用户数据。
由于epoll_data是一个联合体,因此不能同时使用ptr和fd。如果要将文件描述符和用户数据关联起来,以实现数据的快速访问,只能使用其他手段,比如放弃使用epoll_data_t 的fd成员,在ptr指向的用户数据中包含fd。

epoll_wait函数

它在一段超时时间内等待一组文件描述符上的事件。

#include<sys/epoll.h>
int epoll_wait(int epfd,struct epoll_event *events,int maxevents,int timeout);
//返回值为就绪事件描述符的个数
  • events:从内核得到的就绪事件的集合
    这个数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll的数组参数那样既用于传入用户注册的事件,又用于输出内核检测到的就绪事件。极大地提高了应用程序索引就绪文件描述符的效率。
    在这里插入图片描述

  • maxevents:最多从内核中得到maxevents个就绪事件
    如果内核中有<maxevents个就绪事件产生,则将全部的就绪事件复制到events指向的数组中
    如果内核中就绪事件的个数>maxevents,则第一次复制前maxevents个就绪事件到events所指向的数组中,等events中的就绪事件处理了一部分之后再接着从内核中进行复制

  • timeout:超时时间

epoll举例

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<poll.h>
#include<sys/epoll.h>



#define MAXFD 10
int create_sockfd();

void epoll_add(int epfd,int fd)//封装注册事件
{

	struct epoll_event ev;
	ev.data.fd=fd;
	ev.events=EPOLLIN;

	if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
	{
		perror("epoll_ctl error");
	}
}

void epoll_del(int epfd,int fd)//封装删除事件
{
	if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
	{
		perror("epoll_ctl del error");
	}

}

int main()
{
	int sockfd=create_sockfd();
	assert(sockfd!=-1);

	int epfd=epoll_create(MAXFD);//create
	assert(epfd!=-1);

    epoll_add(epfd,sockfd);//将套接字监听字添加到内核事件表中,此时内核事件表中只有一个监听事件
    
	struct epoll_event events[MAXFD];//创建存放就绪事件的数组

	while(1)
	{
		int n=epoll_wait(epfd,events,MAXFD,5000);
		if(n==-1)
		{
			perror("epoll_wait error");
		}
		else if(n==0)
		{
			printf("time out\n");
		}
		else
		{
			int  i=0;
			for(;i<n;++i)//该循环内的所有事件都是就绪的
			{
				int fd=events[i].data.fd;
				if(events[i].events &EPOLLIN)//判断是否是读数据就绪
				{
					if(fd==sockfd)
					{
						//accept
					  struct sockaddr_in  caddr;
					  int len=sizeof(caddr);
					  int c=accept(sockfd,(struct sockaddr *)&caddr,&len);
					  if(c<0)
					  {
						continue;
					  }
					  printf("accept=%d\n",c);
					  epoll_add(epfd,c);

					}
					else
					{
						//recv
						char buff[128]={0};
						int res=recv(fd,buff,127,0);
						if(res<=0)
						{
						/*
                         注意,在内核处理这块与poll和select不同
                         1.删除
                         2.关闭
                        */
							epoll_del(epfd,fd);
							close(fd);
							printf("one client over\n");
						}
					    else
						{
							printf("recv(%d)=%s\n",fd,buff);
							send(fd,"ok",2,0);
						}
					}

				}
			}
		}
	}
}

int create_sockfd()//创建套接字
{
	int sockfd=socket(AF_INET,SOCK_STREAM,0);
	if(sockfd==-1)
	{
		return -1;
	}

	struct sockaddr_in saddr;
	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));
	if(res==-1)
	{
		return -1;
	}

	listen(sockfd,5);
	return sockfd;
}

猜你喜欢

转载自blog.csdn.net/qq_43313035/article/details/89392674