IO复用之epoll

目录

1.epoll就解决了select和poll的三个痛点

2.epoll一组函数的介绍

2.1  epoll_create()

2.2 epoll_ctl()

2.3  epoll_wait()

3.利用epoll实现tcp服务器端

 4.epoll的总结


1.epoll就解决了select和poll的三个痛点

1.从用户空间传递到内核空间,select和poll的开销是非常大的;

2.从内核实现上来讲,select和poll内核实现是轮询,事件复杂度是O(n);

3.select和poll内核检测O(n).所以select和poll就不适合描述符特别多的场景:

2.epoll一组函数的介绍

epoll实际上是一组函数,epoll_creat,epoll_ctl,epoll_wait;

epoll三个函数各自的原型如下:
注意,这些方法都是可以查询帮助手册:

man epoll_creat/ctl/wait;

2.1  epoll_create()

用于创建内核事件表,epoll_create()成功返回内核事件表的文件描述符(文件描述符是一个整型,所以返回一个整型),通过这个返回值我们可以控制内核事件表(比如打开它);失败返回-1

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

size 表示内核事件表的大小;参数现在并不起作用,只是给内核一个提示,告诉它事件表需要多大。

2.2 epoll_ctl()

用于操作内核事件表,这个函数可以给刚才创建的内核事件表添加,修改,移除描述符;epoll_ctl()成功返回 0,失败返回-1

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

第一个参数epfd 就是刚才epoll_creat的返回值,指定要操作的内核事件表的文件描述符
第二个参数op是操作方法,指定操作类型

  • EPOLL_CTL_ADD 往内核事件表中注册 fd 上的事件
  • EPOLL_CTL_MOD修改 fd 上的注册事件
  • EPOLL_CTL_DEL 删除 fd 上的注册事件

第三个参数fd,指定要操作的文件描述符
文件描述符上的事件就在struct epoll_event上来存放;

struct epoll_event

{
     _uint32_t events;//events表示epo11 事件,可以按位存放多类型的事件;因为它是32位的,所以可以表示32个事件;
     epoll_data_t data;//用户数据,其中data是一个联合体,结构在下面: 相当于data.fd存放描述符;

};


其中, events 成员描述事件类型, epoll 支持的事件类型与 poll 基本相同,表示epoll 事件的宏是在 poll 对应的宏前加上'E’,比如 epoll的数据可读事件是EPOLLIN。但是 epoll 有两个额外的事件类型:EPOLLET 和EPOLLONESHOT

data 成员用于存储用户数据,是一个联合体,其定义如下:

typedef union epoll_data
{
    void *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64;
}epoll_data_t;
//其中 fd 成员使用的最多,它指定事件所从属的目标文件描述符。相当于data.fd存放描述符;

2.3  epoll_wait()

用于在一段超时时间内等待一组文件描述符上的事件,epoll_wait()成功返回就绪的文件描述符的个数,失败返回-1,超时返回 0

int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
  • epfd 参数指定要操作的内核事件表的文件描述符
  • events 参数是一个用户数组,这个数组仅仅在 epoll_wait 返回时保存内核检测到的所有就绪事件,而不像select 和 poll 的数组参数那样既用于传入用户注册的事件,又用于输出内核检测到的就绪事件。这就极大地提高了应用程序索引就绪文件描述符的效率。
  • maxevents 参数指定用户数组的大小,即指定最多监听多少个事件,它必须大于0
  • timeout 参数指定超时时间,单位为亳秒,如果 timeout 为 0,则 epoll_wait 会立即返回,如果 timeout为-1,则 epoll_wait 会一直阻塞,直到有事件就绪。

注意,这个超时时间和poll一样,以毫秒为单位;

3.利用epoll实现tcp服务器端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
 #include <assert.h>
 #include <sys/socket.h>
  #include <arpa/inet.h>
  #include <netinet/in.h>
   #include <sys/epoll.h>
 
 #define MAXFD 10

int create_socket()
 {
     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;
     }
 
     res=listen(sockfd,5);
     if(res==-1)
     {
         return -1;
     }
 
     return sockfd;
 }

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)
	{
		perror("epoll ctl add err!\n");
	}
}

void epoll_del(int epfd,int fd)
{
	if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
	{
		perror("epoll ctl del err!\n");
	}
}

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

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

	epoll_add(epfd,sockfd);

	struct epoll_event evs[MAXFD];
	while(1)
	{
		int n=epoll_wait(epfd,evs,MAXFD,5000);
		if(n==-1)
		{
			perror("epoll wait err!\n");
			continue;
		}
		else if(n==0)
		{
			printf("time out!\n");
			continue;
		}
		else
		{
			for(int i=0;i<n;i++)
			{
				int fd=evs[i].data.fd;
				if(evs[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==-1)
						{
							continue;
						}
						
						printf("accept c=%d\n",c);
						epoll_add(epfd,c);
					}
					else
					{
						//recv
						char buff[128]={0};
						int res=recv(fd,buff,127,0);
						if(res<=0)
						{
							epoll_del(epfd,fd);
							close(fd);
							printf("one client over!\n");
						}
						else
						{
							printf("buff(c=%d)=%s\n",fd,buff);
							send(fd,"ok",2,0);
						}

					}
				}
			}
		}

	}

	exit(0);
}

 4.epoll的总结

epoll在实现和使用上与 select、poll有很大差异。首先,epoll 使用一组函数来完成任务,而不是单个函数。其次,epoll 把用户关心的文件描述符上的事件放在内核里的一个事件表中。从而无需像 select 和 poll 那样每次调用都要重复传入文件描述符或事件集。但epoll 需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表.

特别强调:epoll的返回值至关重要

  • 9
    点赞
  • 23
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值