一文读懂epoll

一、基于select的I/O复用技术速度慢的原因

  1.调用select函数后常见的针对所有文件描述符的循环体

  2.每次调用select函数都需要向该函数传递监视对象

select的缺点:调用select函数,监视对象,如果有对象发生改变,则针对所有文件描述符循环,如果超时,就再调用select函数,就反复调用,看监视对象是否有变化。这样每次调用select函数都需要将fd从用户态拷贝到内核态,因为我们的监视对象是文件描述符,文件描述符是内核管理的。而且每次调用select函数都需要在内核遍历所有的文件描述符,即使是只变化一个,也会遍历所有的。select有上限(poll没有上限,因为是链表数据结构).

二、优于select的epoll

  由于上述的select的缺点,所以寻求仅向操作系统传递一次监视对象,监视范围或内容发送变化时只通知发送变化的事项的解决方法。linux的支持方式是epoll,Windows的支持方式是IOCP

下面介绍epoll相关的函数:

epoll_create:创建保存epoll文件描述符的空间

epoll_ctl:向空间注册并注销文件描述符

epoll_wait:与select函数类似,阻塞等待文件描述符发生变化。

linux官方文档:

扫描二维码关注公众号,回复: 5746128 查看本文章

struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };

常见event:

EPOLLIN
              The associated file is available for read(2) operations.

EPOLLOUT
              The associated file is available for write(2) operations.

EPOLLERR
              Error condition happened  on  the  associated  file  descriptor.
              This event is also reported for the write end of a pipe when the
              read end has been closed.  epoll_wait(2) will always report  for
              this event; it is not necessary to set it in events.

EPOLLRDHUP (since Linux 2.6.17)
              Stream socket peer closed connection, or shut down writing  half
              of connection.  (This flag is especially useful for writing sim‐
              ple code to detect peer shutdown when using Edge Triggered moni‐
              toring.)

EPOLLERR
              Error condition happened  on  the  associated  file  descriptor.
              This event is also reported for the write end of a pipe when the
              read end has been closed.  epoll_wait(2) will always report  for
              this event; it is not necessary to set it in events.

EPOLLET
              Sets  the  Edge  Triggered  behavior  for  the  associated  file
              descriptor.  The default behavior for epoll is Level  Triggered.
              See  epoll(7) for more detailed information about Edge and Level
              Triggered event distribution architectures.

#include <sys/epoll.h>

int epoll_create(int size);

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

EPOLL_CTL_ADD
              Register  the  target  file  descriptor fd on the epoll instance
              referred to by the file descriptor epfd and associate the  event
              event with the internal file linked to fd.

EPOLL_CTL_MOD
              Change  the event event associated with the target file descrip‐
              tor fd.

EPOLL_CTL_DEL
              Remove (deregister) the target file descriptor fd from the epoll
              instance  referred  to by epfd.  The event is ignored and can be
              NULL (but see BUGS below).

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

基于epoll的回声服务器:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <sys/epoll.h>

#define BUF_SIZE 100
#define EPOLL_SIZE 50
void error_handling(char *buf);

int main(int argc, char *argv[])
{
	int serv_sock, clnt_sock;
	struct sockaddr_in serv_adr, clnt_adr;
	socklen_t adr_sz;
	int str_len, i;
	char buf[BUF_SIZE];

	struct epoll_event *ep_events;
	struct epoll_event event;
	int epfd, event_cnt;

	if(argc!=2) {
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	serv_sock=socket(PF_INET, SOCK_STREAM, 0);
	memset(&serv_adr, 0, sizeof(serv_adr));
	serv_adr.sin_family=AF_INET;
	serv_adr.sin_addr.s_addr=htonl(INADDR_ANY);
	serv_adr.sin_port=htons(atoi(argv[1]));
	
	if(bind(serv_sock, (struct sockaddr*) &serv_adr, sizeof(serv_adr))==-1)
		error_handling("bind() error");
	if(listen(serv_sock, 5)==-1)
		error_handling("listen() error");
        //创建保存epoll文件描述符的空间
	epfd=epoll_create(EPOLL_SIZE);
        //为ep_events动态分配空间
	ep_events=malloc(sizeof(struct epoll_event)*EPOLL_SIZE);
        //设置并注册事件
	event.events=EPOLLIN;
	event.data.fd=serv_sock;	
        //向epfd中添加serv_sock的事件event
	epoll_ctl(epfd, EPOLL_CTL_ADD, serv_sock, &event);

	while(1)
	{
                //调用epoll_wait函数,与调用select函数类似,返回发生事件的文件描述符数
		event_cnt=epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
		if(event_cnt==-1)
		{
			puts("epoll_wait() error");
			break;
		}
                //对发生事件的文件描述符进行循环
		for(i=0; i<event_cnt; i++)
		{
			if(ep_events[i].data.fd==serv_sock)
			{
				adr_sz=sizeof(clnt_adr);
				clnt_sock=
					accept(serv_sock, (struct sockaddr*)&clnt_adr, &adr_sz);
				event.events=EPOLLIN;
				event.data.fd=clnt_sock;
				epoll_ctl(epfd, EPOLL_CTL_ADD, clnt_sock, &event);
				printf("connected client: %d \n", clnt_sock);
			}
			else
			{
					str_len=read(ep_events[i].data.fd, buf, BUF_SIZE);
					if(str_len==0)    // close request!
					{
						epoll_ctl(
							epfd, EPOLL_CTL_DEL, ep_events[i].data.fd, NULL);
						close(ep_events[i].data.fd);
						printf("closed client: %d \n", ep_events[i].data.fd);
					}
					else
					{
						write(ep_events[i].data.fd, buf, str_len);    // echo!
					}
	
			}
		}
	}
	close(serv_sock);
	close(epfd);
	return 0;
}

void error_handling(char *buf)
{
	fputs(buf, stderr);
	fputc('\n', stderr);
	exit(1);
}

三、条件触发与边缘触发

  条件触发与边缘触发的区别在于:LT模式下,只要这个fd还有数据可读,每次 epoll_wait都会返回它的事件,提醒用户程序去操作,而在ET(边缘触发)模式中,它只会提示一次,直到下次再有数据流入之前都不会再提示了,无 论fd中是否还有数据可读。所以在ET模式下,read一个fd的时候一定要把它的buffer读光,也就是说一直读到read的返回值小于请求值,或者 遇到EAGAIN错误。还有一个特点是,epoll使用“事件”的就绪通知方式,通过epoll_ctl注册fd,一旦该fd就绪,内核就会采用类似callback的回调机制来激活该fd,epoll_wait便可以收到通知。

猜你喜欢

转载自blog.csdn.net/qq_23905237/article/details/87919115