epoll模型

linux 的网络编程中,很长的时间都在使用 select 来做事件触发。在 linux 新的内核中, 有了一种替换它的机制,就是 epoll

相比于 selectepoll 最大的好处在于它不会随着监听 fd 数目的增长而降低效率。因为在内 核中的 select 实现中,它是采用轮询来处理的,轮询的 fd 数目越多,自然耗时越多。并且,

linux/posix_types.h 头文件有这样的声明:

#define __FD_SETSIZE 1024

表示 select 最多同时监听 1024 fd,当然,可以通过修改头文件再重编译内核来扩大这个 数目,但这似乎并不治本。

epoll 的接口非常简单,一共就三个函数:

1.int epoll_create(int size);

创建一个 epoll 的句柄,size 用来告诉内核这个监听的数目一共有多大。这个参数不同于 select()中的第一个参数,给出最大监听的 fd+1 的值。需要注意的是,当创建好 epoll 句柄后,它就是会占用一个 fd 值,在 linux 下如果查看/proc/进程 id/fd/,是能够看到这个 fd 的,所 以在使用完 epoll 后,必须调用 close()关闭,否则可能导致 fd 被耗尽。但在目前新的内核中,size的大小可以不用管了,不用去定义,可以默认为1,这时候系统会默认读取size的大小

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

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

第一个参数是 epoll_create()的返回值,

第二个参数表示动作,用三个宏来表示: EPOLL_CTL_ADD:注册新的 fd epfd 中; EPOLL_CTL_MOD:修改已经注册的 fd 的监听事件; EPOLL_CTL_DEL:从 epfd 中删除一个 fd

第三个参数是需要监听的 fd

第四个参数是告诉内核需要监听什么事,struct epoll_event 结构如下: 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 表示已超时。

man 手册中,得到 ET LT 的具体描述如下

EPOLL 事件有两种模型:

Edge Triggered (ET) 边缘触发 只有数据到来,才触发,不管缓存区中是否还有数据。 Level Triggered (LT) 水平触发 只要有数据都会触发。

假如有这样一个例子:

1.我们已经把一个用来从管道中读取数据的文件句柄(RFD)添加到 epoll 描述符

2.这个时候从管道的另一端被写入了 2KB 的数据

3.调用 epoll_wait(2),并且它会返回 RFD,说明它已经准备好读取操作

4.然后我们读取了 1KB 的数据

5.调用 epoll_wait(2). .... 

下面以我自己写的一个服务端和客户端实现聊天功能为例子:

服务器端:tcp_chat

#include "func.h"
void change_noblock(int fd)
{
	int status;
	status=fcntl(fd,F_GETFL);
	status=status|O_NONBLOCK;
	int ret=fcntl(fd,F_SETFL,status);
	if(-1==ret)
	{
		perror("fcntl");
		return;
	}
}

int main(int argc,char* argv[])
{
	if(argc!=3)
	{
		printf("error args\n");
		return -1;
	}
	int sfd;
	sfd=socket(AF_INET,SOCK_STREAM,0);
	if(-1==sfd)
	{
		perror("socket");
		return -1;
	}
	struct sockaddr_in ser;
	memset(&ser,0,sizeof(ser));
	ser.sin_family=AF_INET;
	ser.sin_port=htons(atoi(argv[2]));//port转网络字节序
	ser.sin_addr.s_addr=inet_addr(argv[1]);//IP地址转网络字节序
	int ret;
	ret=bind(sfd,(struct sockaddr*)&ser,sizeof(ser));//绑定ip地址和端口号
	if(-1==ret)
	{
		perror("bind");
		return -1;
	}
	listen(sfd,10);
	int epfd=epoll_create(1);
	struct epoll_event event,evs[3];
	memset(&event,0,sizeof(event));
	event.events=EPOLLIN;//描述符是否可读
	event.data.fd=sfd;
	ret=epoll_ctl(epfd,EPOLL_CTL_ADD,sfd,&event);
	if(-1==ret)
	{
		perror("epoll_ctl");
		return -1;
	}
	memset(&event,0,sizeof(event));
	event.events=EPOLLIN;//描述符是否可读
	event.data.fd=STDIN_FILENO;//监控标准输入
	ret=epoll_ctl(epfd,EPOLL_CTL_ADD,STDIN_FILENO,&event);
	if(-1==ret)
	{
		perror("epoll_ctl1");
		return -1;
	}
	struct sockaddr_in cli;
	memset(&cli,0,sizeof(cli));
	char buf[5]={0};
	int new_fd=-1;
	int i;
	while(1)
	{
		memset(evs,0,sizeof(evs));
		ret=epoll_wait(epfd,evs,3,-1);
		if(ret>0)
		{
			for(i=0;i<ret;i++)
			{
				if(evs[i].data.fd==sfd)
				{
					int addrlen=sizeof(cli);
					new_fd=accept(sfd,(struct sockaddr*)&cli,&addrlen);
					if(-1==new_fd)
					{
						perror("accept");
						return -1;
					}
					printf("client ip=%s,client port=%d\n",inet_ntoa(cli.sin_addr),ntohs(cli.sin_port));
					change_noblock(new_fd);
					memset(&event,0,sizeof(event));
					event.events=EPOLLIN|EPOLLET;//描述符是否可读
					event.data.fd=new_fd;
					ret=epoll_ctl(epfd,EPOLL_CTL_ADD,new_fd,&event);
				}
				if(evs[i].data.fd==new_fd)
				{
					while(1)
					{
						memset(buf,0,sizeof(buf));
						ret=recv(new_fd,buf,sizeof(buf),0);
						if(-1==ret && errno==EAGAIN)
						{
							break;//读空缓冲区
						}else if(0==ret)
						{
							printf("byebye");
							close(new_fd);
							memset(&event,0,sizeof(event));
							event.events=EPOLLIN;//描述符是否可读
							event.data.fd=new_fd;
							ret=epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event);
							break;
						}
						printf("%s",buf);
					}
					printf("\n");
				}
				if(evs[i].events==EPOLLIN && evs[i].data.fd==0)
				{
					memset(buf,0,sizeof(buf));
					ret=read(STDIN_FILENO,buf,sizeof(buf));
					if(ret<=0)
					{
						printf("byebye\n");
						close(new_fd);
						memset(&event,0,sizeof(event));
						event.events=EPOLLIN;//描述符是否可读
						event.data.fd=new_fd;
						ret=epoll_ctl(epfd,EPOLL_CTL_DEL,new_fd,&event);
						continue;
					}
					ret=send(new_fd,buf,strlen(buf)-1,0);
					if(-1==ret)
					{
						perror("send");
						return -1;
					}
				}
			}
		}
	}
	close(sfd);
	return 0;
}
客户端client_chat:

#include "func.h"

int main(int argc,char* argv[])
{
	if(argc!=3)
	{
		printf("error args\n");
		return -1;
	}
	int sfd;
	sfd=socket(AF_INET,SOCK_STREAM,0);
	if(-1==sfd)
	{
		perror("socket");
		return -1;
	}
	struct sockaddr_in ser;
	memset(&ser,0,sizeof(ser));
	ser.sin_family=AF_INET;
	ser.sin_port=htons(atoi(argv[2]));//port转网络字节序
	ser.sin_addr.s_addr=inet_addr(argv[1]);//IP地址转网络字节序
	int ret;
	ret=connect(sfd,(struct sockaddr*)&ser,sizeof(ser));
	if(-1==ret)
	{
		perror("connect");
		return -1;
	}
	char buf[50]={0};
	fd_set rdset;
	while(1)
	{
		FD_ZERO(&rdset);//清空集合
		FD_SET(STDIN_FILENO,&rdset);
		FD_SET(sfd,&rdset);
		ret=select(sfd+1,&rdset,NULL,NULL,NULL);
		if(ret>0)
		{
			if(FD_ISSET(STDIN_FILENO,&rdset))
			{
				memset(buf,0,sizeof(buf));
				ret=read(0,buf,sizeof(buf));				
				if(ret<=0)
				{
					printf("byebye\n");
					break;
				}
				ret=send(sfd,buf,strlen(buf)-1,0);
				if(-1==ret)
				{
					perror("send");
					return -1;
				}
			}
			if(FD_ISSET(sfd,&rdset))
			{
				memset(buf,0,sizeof(buf));
				ret=recv(sfd,buf,sizeof(buf),0);
				if(-1==ret)
				{
					perror("recv");
					return -1;
				}else if(0==ret)
				{
					printf("byebye\n");
					break;
				}
				printf("%s\n",buf);
			}
		}
	}
	close(sfd);
	return 0;
}


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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值