I/O复用方法——epoll


select 和poll的问题

  1. 找到就绪描述符 遍历所有描述符 ,时间复杂度O(n);
  2. 内核中是轮询的方式,时间复杂度O(n);
  3. 每次都需要向内核传递描述符和事件;

epoll 处理较多的客户端数目

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

◼ epoll_create()用于创建内核事件表;
◼ epoll_ctl()用于操作内核事件表;
◼ epoll_wait()用于在一段超时时间内等待一组文件描述符上的事件;

读事件 EPOLLIN
写事件 EPOLLOUT

写事件,一开始就是就绪的,
发送缓冲区为空,send() 不会阻塞;
接受缓冲区有数据才能recv(), 可能会阻塞。

epoll的两种模式:

ET (epoll )高效模式 边沿触发
LT (select 、poll 、epoll ) 水平触发

LT模式是默认的工作模式,这种模式下的epoll相当于一个效率较高的poll。

当往内核事件表中注册一个文件描述符上的EPOLLET事件,epoll将以ET模式来操作该文件描述符。ET模式是epoll的高效工作模式。
ET和LT的区别:

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

对于采用ET工作模式的文件描述符,当epoll _wait 检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll _wait 调用将不会向应用程序通知这一事件。

可见,ET模式在很大程度上降低了同一个opoll事件被重复触发的次数,因此效率要比LT模式高。

LT模式

(以一次只读一个数据为例)

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include<sys/socket.h>
  7 #include<netinet/in.h>
  8 #include<arpa/inet.h>
  9 #include<sys/epoll.h>
 10 
 11 #define MAX 10
 12 
 13 void epoll_add(int epfd,int fd)//向内核事件表添加文件描述符
 14 {
 15     struct epoll_event ev;
 16     ev.data.fd=fd;
 17     ev.events=EPOLLIN;
 18     if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
 19     {
 20         perror("epoll ctl add err");
 21     }
 22 }
 23 void epoll_del(int epfd,int fd)//移除文件描述符
 24 {
 25 	if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
 26		{
 27 	perror("epoll ctl del err");
 28 	}
 29 }
 30 
 31 int socket_init();//创建监听套介子
 32 int main()
 33 {
 34 	int sockfd=socket_init();
 35 	assert(sockfd!=-1);
 36 
 37  	int epfd=epoll_create(MAX);//创建内核事件表--红黑树
 38     assert(epfd!=-1);
 39 
 40     epoll_add(epfd,sockfd);//将sockfd添加到内核事件表中
 42
 43     struct epoll_event evs[MAX];//收集就绪事件
 44 
 45     while(1)
 46     {
 47         int n=epoll_wait(epfd,evs,MAX,5000);
 48         if(n==-1)
 49         {
 50         continue;
 51         }
 52         else if(n==0)
 53         {
 54         printf("time out\n");
 55         continue;
 56         }
 57         else
 58     {
 59         for(int i=0;i<n;i++)
 60         {
 61             int fd=evs[i].data.fd;
 62             if(evs[i].events&EPOLLIN)//读事件
 63             {
 64                 if(fd==sockfd)//客户端连接我了  accept
 65                 {
 66 
 67                 struct sockaddr_in caddr;
 68                 int len=sizeof(caddr);
 69                 int c=accept(fd,(struct sockaddr*)&caddr,&len);
 70                 if(c<0)
 71                 {
 72                     continue;
 73                 }
 74                 printf("accept c=%d\n",c);
 75                 epoll_add(epfd,c);
 76                 }
 77                 else//客户端给我发数据了   recv
 78                 	{
 79                 	char buff[128]={0};
 80                		int num=recv(fd,buff,1,0);
 81                 	if(num<=0)//客户端关闭了
 82                 	{
 83                     	epoll_del(epfd,fd);              //先移除,后关闭
 84                     	close(fd);
 85                     	printf("cilent close\n");
 86                 }
 87                 else
 88                		 {
 89                     printf("buff=%s\n",buff);
 90                     send(fd,"ok",2,0);
 91                 	}
 92                 }
 93             }
 94         }
 95      }
 96    }
 97 }
 98 int socket_init()
 99 {
100     int sockfd=socket(AF_INET,SOCK_STREAM,0);//创建套接子
101     if(sockfd==-1)
102     {
103         return -1;
104     }
105 
106     struct sockaddr_in saddr;
107     memset(&saddr,0,sizeof(saddr));
108     saddr.sin_family=AF_INET;
109     saddr.sin_port=htons(6000);
110     saddr.sin_addr.s_addr=inet_addr("127.0.0.1");
111 
112     int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));
113     if(res==-1)
114     {
115         return -1;
116     }
117 
118     if(listen(sockfd,5)==-1)
119     {
120         return -1;
121     }
122     return sockfd;
123 }                                                                            

在这里插入图片描述
epoll_wait 返回6次。

ET模式

(以一次只读一个数据为例)

当往内核事件表中注册一个文件描述符上的EPOLLET事件,epoll将以ET模式来操作该文件描述符。

在这里插入图片描述
数据还在缓冲区中,数据没有读完,因为没有后续事件所以会被阻塞。

ET模式,设置为非阻塞

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<unistd.h>
  4 #include<string.h>
  5 #include<assert.h>
  6 #include<sys/socket.h>
  7 #include<netinet/in.h>
  8 #include<arpa/inet.h>
  9 #include<sys/epoll.h>
 10 #include<fcntl.h>
 11 #include<errno.h>
 12 
 13 #define MAX 10
 14 
 15 void setnonblock(int fd)//将文件描述符设置为非阻塞
 16 {
 17     int oldfl=fcntl(fd,F_GETFL);
 18     int newfl=oldfl |O_NONBLOCK;
 19    if(fcntl(fd,F_SETFL,newfl)==-1)
 20    {
 21        printf("fcntl error\n");
 22    }
 23 }
 24 void epoll_add(int epfd,int fd)//向内核事件表添加文件描述符
 25 {
 26     struct epoll_event ev;
 27     ev.data.fd=fd;
 28     ev.events=EPOLLIN|EPOLLET;
 29 
 30     setnonblock(fd);//设置非阻塞
 31 
 32     if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
 33     {
 34         perror("epoll ctl add err");
 35     }
 36 }
 37 void epoll_del(int epfd,int fd)//移除文件描述符
 38 {
 39 	if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
 40 	{
 41 		perror("epoll ctl del err");
 42 	}
 43 }
 44 
 45 int socket_init();//创建监听套介子
 46 int main()
 47 {
 48 	int sockfd=socket_init();
 49 	assert(sockfd!=-1);
 50 
 51  	int epfd=epoll_create(MAX);//创建内核事件表--红黑树
 52     assert(epfd!=-1);
 53 
 54     epoll_add(epfd,sockfd);//将sockfd添加到内核事件表中
 55 
 57     struct epoll_event evs[MAX];//收集就绪事件
 58     while(1)
 60     {
 61         int n=epoll_wait(epfd,evs,MAX,5000);
 62         if(n==-1)
 63         {
 64         continue;
 65         }
 66         else if(n==0)
 67         {
 68         printf("time out\n");
 69         continue;
 70         }
 71         else
 72         {
 73         for(int i=0;i<n;i++)
 74         {
 75             int fd=evs[i].data.fd;
 76             if(evs[i].events&EPOLLIN)//读事件
 77             {
 78                 if(fd==sockfd)//客户端连接我了  accept
 79                 {
 80 
 81                 struct sockaddr_in caddr;
 82                 int len=sizeof(caddr);
 83                 int c=accept(fd,(struct sockaddr*)&caddr,&len);
 84                 if(c<0)
 85                 {
 86                     continue;
 87                 }
 88                 printf("accept c=%d\n",c);
 89                 epoll_add(epfd,c);
 90                 }
 91                 else//客户端给我发数据了   recv
 92                 {
 93                  while(1)
 94                      //这段代码不会被重复触发,所以我们循环读取数据,把缓冲区中的数据读完
 95                  {
 96                 char buff[128]={0};
 97                 int num=recv(fd,buff,1,0);
 98                  if(num==-1)
 99                 {
100                     if(errno==EAGAIN||errno==EWOULDBLOCK)
101                     {
102                     send(fd,"ok",2,0);
103                     }
104                     else
105                     {
106                         printf("recv error\n");
107                     }
108                     break;
109                 }
110                 else if(num==0)//客户端关闭了
111                 {
112                     epoll_del(epfd,fd);              //先移除,后关闭
113                     close(fd);
114                     printf("cilent close\n");
115                     break;
116                 }
117                 else
118                 {
119                     printf("buff=%s\n",buff);
120                 }
121                  }
122                 }
123             }
124         }
125     }
126     }
127 }
-- 插入 --                                              

在这里插入图片描述
epoll_wait 返回两次。

select 、poll 、epoll 的区别

在这里插入图片描述

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值