I/O函数复用 -- epoll

内核事件表

epoll函数是linux特有的I/O复用函数。它再在实现和使用上与select、poll有一下差异:

  1. epoll使用一组函数来完成任务,而不是单个函数
  2. epoll把用户关心的文件描述符上的事件放在内核的一个事件表中,从而无需像select和poll那样每次调用都要重复传入文件描述符集或事件集。

epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。这个文件描述符使用epoll_create函数来创建:

# include<sys/epoll.h>
int epoll_create(int size);
  • 下面的函数用来操作epoll的内核事件表:
# incldue<sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);
fd参数是要操作的文件描述符,op参数则指定操作类型,有以下3种:
1、EPOLL_CTL_ADD,往时间表中注册fd上的事件
2、EPOLL_CTL_MOD,修改fd上的注册事件
3、EPOLL_CTL_DEL,删除fd上的注册事件
event参数指定事件,他是epoll_event结构指针类型。定义如下:
struct epoll_event
{
    _uint32_t events;//epoll事件
    epoll_data_t data;//用户数据
};
其中events成员描述事件类型。epoll支持的事件类型和poll基本相同。表示epoll事件类型的宏是在poll对应的宏前加上E,比如epoll的数据可读事件是EPOLLIN,但epoll有两个额外的事件类型——EPOLLET和EPOLLONESHOT。它们对于epoll的高效运作非常关键。data成员用于存储用户数据,其类型epoll_data_t的定义如下:
typedef union epoll_data
{
    vpod *ptr;
    int fd;
    uint32_t u32;
    uint64_t u64
}epoll_data_t;

epoll_data_t是一个联合体,其4个成员中使用最多的是fd,它指定事件所属的目标文件描述符。ptr成员可用来指定与fd相关的用户函数。但由于epoll_data_t是一个联合体,我们不能同时使用ptr成员和fd成员,因此,如果要将文件描述符和用户数据关联起来,以实现快速的数据访问,只能使用其他手段,比如放弃使用fd成员,而在ptr指向的用户数据中包含fd
epoll_ctl成功时返回0,失败时返回-1,并设置errno

epoll_wait函数

epoll系列系统调用的主要接口是epoll_wait函数。它在一段超时时间内等待一组文件描述符上的事件,其原型如下:

# include<sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

        该函数成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno;timeout参数是超时时间。maxevents参数指定最多监听多少个事件,它必须大于0。

       epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表中复制到它的第二个参数events指向的数组中。这个数组只用于输出wpoll_wait检测到的就绪事件,而不向select和poll的数组参数那样即用于传入用户注册的事件,又用于输出内核检测到的就绪事件。这就极大地提高了应用程序索引就绪文件描述符的效率。

LT和ET模式

       epoll对文件描述符的操作有两种模式:LT(电平触发)和ET(边沿触发)模式。LT模式是默认的工作模式,这种模式下epoll相当于一个效率较高的poll。当往epoll内核事件表中注册一个文件描述符上的EPOLLET事件时,epoll将以ET模式来操作该文件描述符,ET模式是epoll的高效工作模式。

       对于采用LT工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到该事件被处理。

      而对于采用ET工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。可见ET模式在很大程度上降低了同一个epoll事件被重复调用触发的次数,因此效率要比LT模式高。

代码清单

  • LT模式下的服务器:
# include<stdio.h>
# include<sys/epoll.h>
# include<sys/socket.h>
# include<assert.h>
# include<arpa/inet.h>
# include<string.h>
# include<unistd.h>
# include<sys/socket.h>
# include<stdlib.h>
# include<error.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)
	{
		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\n");
	}
}
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);

	int epfd = epoll_create(MAXFD);
	epoll_add(epfd, sockfd);
	struct epoll_event events[MAXFD];

	while(1)
	{
		int n = epoll_wait(epfd, events, MAXFD, 5000);
		if(n == -1)
		{
			printf("epoll_wait error\n");
			continue;
		}
		else if(n == 0)
		{
			printf("time out\n");
			continue;
		}
		else
		{
			int i = 0;
			for(; i < n; i++)
			{
				int fd = events[i].data.fd;
				if(events[i].events & EPOLLIN)
				{
					if(fd == sockfd)
					{
						int len = sizeof(caddr);
						int c = accept(sockfd, (struct sockaddr*)&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(epfd, fd);
							close(fd);
							printf("one client over\n");
							continue;
						}
						else
						{
							printf("recv(%d) = %s\n",fd, buff);
							send(fd, "ok", 2, 0);
						}
					}
				}

			}
		}
	}
}
  • ET模式下的服务器:
# include<stdio.h>
# include<sys/epoll.h>
# include<sys/socket.h>
# include<assert.h>
# include<arpa/inet.h>
# include<string.h>
# include<unistd.h>
# include<sys/socket.h>
# include<stdlib.h>
# include<errno.h>
# include<fcntl.h>
#define MAXFD 10

void setnonblock(int fd)
{
	int oldfl = fcntl(fd, F_GETFL);
	int newfl = oldfl | O_NONBLOCK;
	if(fcntl(fd, F_SETFL, newfl) == -1)
	{
		perror("fcntl error\n");
	}
}
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 error");
	}
	setnonblock(fd);
}
void epoll_del(int epfd, int fd)
{
	if(epoll_ctl(epfd, EPOLL_CTL_DEL, fd, NULL) == -1)
	{
		perror("epoll ctl del error\n");
	}
}
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);

	int epfd = epoll_create(MAXFD);
	epoll_add(epfd, sockfd);
	struct epoll_event events[MAXFD];

	while(1)
	{
		printf("epoll_wait\n");
		int n = epoll_wait(epfd, events, MAXFD, 5000);
		if(n == -1)
		{
			printf("epoll_wait error\n");
			continue;
		}
		else if(n == 0)
		{
			printf("time out\n");
			continue;
		}
		else
		{
			int i = 0;
			for(; i < n; i++)
			{
				int fd = events[i].data.fd;
				if(events[i].events & EPOLLIN)
				{
					if(fd == sockfd)
					{
						int len = sizeof(caddr);
						int c = accept(sockfd, (struct sockaddr*)&caddr, &len);
						if( c < 0)
						{
							continue;
						}
						printf("accept c = %d\n", c);
						epoll_add(epfd, c);
					}
					else
					{
						while(1)
						{
							char buff[128] = {0};
							int num = recv(fd, buff, 1, 0);
							if(num == -1)
							{
								if(errno == EAGAIN || errno == EWOULDBLOCK)
								{
									send(fd, "ok", 2, 0);
								}
								break;
							}
							else if(num == 0)
							{
								epoll_del(epfd, fd);
								close(fd);
								printf("one client over\n");
								break;
							}
							printf("recv %d = %s\n",fd, buff);
						}
					}
				}
			}
		}
	}
}

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值