高级I/O---多路复用---epoll

多路复用之epoll


作为多路复用中最高效的I/O,epoll有着select和poll都不具有的很多能力。

不同于poll和select,epoll它用三个函数来实现多路复用这一个功能。

    #include <sys/epoll.h>

       int epoll_create(int size);
       //用于创建一个epoll模式的存储空间,返回值是一个文件描述符,后面和函数中
       //都会用到这个epoll_fd。
        
       int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
       //epoll_ctl用于添加一个事件到epfd中,op表示方式有EPOLL_ADD,EPOLL_DEL
       //EPOLL_MOD方式,fd表示你要添加进去的文件描述符,后面是一个结构体指针,
       //结构体在下面会说到。
      
       int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
       //epoll_wait用于等待事件的发生,这个结构体指针会储存返回来的fd,maxevents
       //表示最大能够接收到的fd的个数,注意能接收到的fd的个数很多(查阅一些资料这个数据
       //在1G内存的机子上大约能有10万余个)。
                  
           typedef union epoll_data {
               void        *ptr;
               int          fd;
               __uint32_t   u32;
               __uint64_t   u64;
           } epoll_data_t;

           struct epoll_event {
               __uint32_t   events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };
           //这个结构体中包含一个联合体和一个events,events是用来描述触发状态,
           //可以设置为EPOLLIN,EPOLLOUT,EPOLLD等。
           //联合体中我们关注fd和*ptr因为联合体有时候会有bug产生,如果我们用str来
           //存放读取的数据的时候,我们可以让这个ptr指向一个结构体,结构体中设置
           //fd和*buf参数。

epoll之所以比之前的多路复用高效主要原因有以下几个

1>:epoll的组织方式,它是以两个一个很高效的结构体红黑树,list链表

红黑树用于存放fd,一旦有事件发生它能够以O(1)的时间复杂度找到并且

把它放到list中,这样发回值就是这个event结构体指针,它里面存放的

便是发生事件的fd,从之前多路复用的轮训O(N),减少到O(1),可见它的

高效之处。

2>:触发方式:epoll可以使用两种触发方式来获取事件;下面会说到的水平

触发和边缘触发。使用边缘触发方式可以使epoll更加高效。

边缘触发和水平触发


水平触发PT:epoll_wait一旦fd中发生状态变化假设状态变化是read,并且如果没有读完,下次会继续提醒,直到把缓冲区fd中的数据读完为止。

边缘触发ET:epoll_wait当fd中状态发生变化时假设状态变化是read,它会在第一次提醒,如果没有把它读完则后面不会再提醒,除非有新的数据到来才会接着上次的往后面读。

在epoll_wait下要将套接字用fcntl函数设置为非阻塞,为什么要设置未非阻塞呢?想了好久的我终于发现,在一次ET中因为并不保证把缓冲区中的数据彻底读完,而阻塞模式下的sock是要保证把fd中的数据读完的,两者矛盾,会导致不接受新到来的fd。

ET需要用到的是自定义的read函数,如下所示:

 57 int read_fd(int sock,char *buf,int size)
 58 {
 59     int _size=-1;
 60     int index=0;
 61     while((_size=read(sock,buf+index,size-index)))
 62     {
 63         if(_size<0&&errno==EAGAIN)
 64         {
 65             break;
 66         }
 67         index+=_size;
 68         _size=-1;
 69     }
 70     return index;
 71 }
 //因为ET模式的特点必须保证一次把整个sock中的一次数据全部读完,不然如果没有下次的数据
 //到来,前面没有读完的数据就会永久性的丢失了。
 //当read读到sock中整个数据流的最末尾的时候会产生一个类似errno的信号EAGAIN告诉它已经
 //读到了sock中的最后一个数据。


下面是一个epollET模式下的client与server

  1 #include<stdio.h>
  2 #include<stdlib.h>
  3 #include<sys/socket.h>
  4 #include<sys/types.h>
  5 #include<arpa/inet.h>
  6 #include<netinet/in.h>
  7 #include<sys/epoll.h>
  8 #include<fcntl.h>
  9 #include<errno.h>
 10 #include<string.h>
 11 #include<unistd.h>
 12 
 13 #define _MAX_LISTEN_ 6
 14 #define _MAX_EPFD_ 64
 15 #define _MAX_BUF_ 1024
 16 
 17 void nonblock(int sock)
 18 {
 19     int fl=fcntl(sock,F_GETFL);
 20     if(fl<0)
 21     {
 22         perror("fcntl");
 23         exit(2);
  24     }
 25     if(fcntl(sock,F_SETFL,fl|O_NONBLOCK)<0)
 26     {
 27         exit(3);
 28     }
 29 }
 30 int Listensock(char *ip,int port)
 31 {
 32     int sock=socket(AF_INET,SOCK_STREAM,0);
 33     if(sock<0)
 34     {
 35         perror("socket");
 36         exit(1);
 37     }
 38     nonblock(sock);
 39     struct sockaddr_in local;
 40     local.sin_addr.s_addr=inet_addr(ip);
 41     local.sin_port=htons(port);
 42 
 43     if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
 44     {
 45         perror("bind");
 46     }
 47 
 48     if(listen(sock,_MAX_LISTEN_)<0)
 49     {
 50         perror("listen");
 51     }   }
 52 
 53     return sock;
 54 
 55 }
 56 
 57 int read_fd(int sock,char *buf,int size)
 58 {
 59     int _size=-1;
 60     int index=0;
 61     while((_size=read(sock,buf+index,size-index)))
 62     {
 63         if(_size<0&&errno==EAGAIN)
 64         {
 65             break;
 66         }
 67         index+=_size;
 68         _size=-1;
 69     }
 70     return index;
 71 }
 72 void epollserver(int sock)
 73 {
 74     int epfd=epoll_create(256);
 75 
 76     if(epfd<0)
 77     {
 78         perror("epoll_create");
 79         exit(4);
 80     }
 81 
 82     struct epoll_event ev;
 83     ev.data.fd=sock;
 84     ev.events=EPOLLIN|EPOLLET;
 85 
 86     if(epoll_ctl(epfd,EPOLL_CTL_ADD,sock,&ev)<0)
 87     {
 88         perror("epoll_ctl");
 89         exit(5);
 90     }
 91     struct epoll_event epfds[_MAX_EPFD_];
 92 
 93     int fd=-1;
 94     int i;
 95     for(i=0;i<_MAX_EPFD_;i++)
 96     {
 97         epfds[i].data.fd=fd;
 98     }
 99     int timeout=5000;
 100 
 101     int fdlen=0;
 102     while(1)
 103     {
 104         switch(fdlen=epoll_wait(epfd,epfds,_MAX_EPFD_,timeout))
 105         {
 106             case -1:
 107                 {
 108                     perror("epoll_wait");
 109                     continue;
 110                 }
 111             case 0:
 112                 {
 113                     printf("timeout\n");
 114                 }
 115             default:
 116                 {
 117                     struct sockaddr_in client;
 118                     int client_len=sizeof(client);
 119                     int i=0;
 120                     for(i=0;i<fdlen;i++)
 121                     {
122                         int retfd=epfds[i].data.fd; 
123                         if(retfd==sock&&(epfds[i].events&EPOLLIN))
124                         {                         
125                             int recvfd=accept(sock,(struct sockaddr*)126                                     &client,&client_len);
127                             if(recvfd<0)
128                             {
129                                 continue;
130                             }
131                             else
132                             {
133                                 nonblock(recvfd);
134                                 ev.data.fd=recvfd;
135                                 ev.events=EPOLLIN|EPOLLET;
136                                 if(epoll_ctl(epfd,EPOLL_CTL_ADD,recvfd,&ev)<0)
137                                 {
138                                     perror("epoll_ctl");
139                                     continue;
140                                 }
141                                 printf("a client come..ip=%s\n",142                                         inet_ntoa(client.sin_addr));
143 
144                             }
145                         }
146                         else if (epfds[i].events&EPOLLIN)
147                         {                        {
148                             char buf[_MAX_BUF_];
149                             memset(buf,‘\0‘,_MAX_BUF_);
150                             int ret=read_fd(retfd,buf,_MAX_BUF_);
151                             if(ret>0)
152                             {
153                                 buf[ret-1]=‘\0‘;
154                                 printf("client ::%s\n",buf);
155                                 fflush(stdout);
156                             }else if(ret==0){
157                                 printf("ip=%s client is leave...\n",158                                         inet_ntoa(client.sin_addr));
159                             }else{
160                                 //doing noting
161                             }
162                         }//else{此处可以改成回显}
163                 }
164             }
165 
166     }
167 }
168 
169 int main(int argc,char *argv[])
170 {
171     if(argc!=3)     if(argc!=3)
172     {
173         printf("[%s][ip][port]\n",argv[0]);
174     }
175     char *ip=argv[1];
176     int port=atoi(argv[2]);
177     int sock=Listensock(ip,port);
178 
179     int opt=1;
180 
181     if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0)
182     {
183         perror("setsockopt");
184     }
185 
186     epollserver(sock);
187 
188     return 0;
189 }

改为回显模式:

当读完client的数据后,可以将event改为EPOLLOUT然后将读到的数据保存起来

当下次写事件发生时,将保存的数据扔出去。

注意:因为结构体中的data是一个联合体,当我们存放完fd后再去存放ptr有可能

会有bug,这里的方法是让ptr指向一个结构体,这个结构体中保存着fd和buf。

typedef struct p_buf{
      int fd;
      char outbuf[_MAX_BUF_];
}p_buf;
//自定义的缓冲区

164                        else if (epfds[i].events&EPOLLIN)
166                         {
167                             ptrbuf *p_buf=(ptrbuf *)malloc(sizeof(ptrbuf));
                                //将数据直接读到自定义的缓冲区中
168                             memset(p_buf->outbuf,‘\0‘,sizeof(p_buf->outbuf));
169                             p_buf->fd=retfd;
170                             int ret=read_fd(retfd,p_buf->outbuf,_MAX_BUF_);
171 
172                             if(ret>0)
173                             {
174                                 p_buf->outbuf[ret-1]=‘\0‘;
175                                 printf("client ::%s\n",p_buf->outbuf);
176                                 fflush(stdout);
177                                 ev.events=EPOLLOUT|EPOLLET;
178                                 ev.data.ptr=p_buf;
                                      //设置为EPOLLOUT模式
179                                 if(epoll_ctl(epfd,EPOLL_CTL_MOD,retfd,&ev)<0)
180                                 {
181                                     perror("epoll_ctl");
182                                     continue;
183                                 }
184                             }else if(ret==0){
185                                 if(epoll_ctl(epfd,EPOLL_CTL_DEL,retfd,NULL)<0)
186                                 {
187                                     perror("epoll_ctl");
188                                 }
189                                 printf("ip=%s client is leave...\n",190                                         inet_ntoa(client.sin_addr));
191                             }else{
192                                 //doing noting
193                             }
194                         }else{//当写条件满足时,回显消息并且改回为EPOLLIN模式
195                             ptrbuf* outptr=(ptrbuf*)epfds[i].data.ptr;
196                             int outfd=outptr->fd;
197                             outptr->outbuf[strlen(outptr->outbuf)]=‘\n‘;
198                             out_write(outfd,outptr->outbuf,_MAX_BUF_);
199                             free(outptr);
200                             ev.events=EPOLLIN|EPOLLET;
201                             ev.data.fd=outfd;
202                             if(epoll_ctl(epfd,EPOLL_CTL_MOD,outfd,&ev)<0)
203                             {
204                                 perror("epoll_ctl");
205                             }
206                         }
207                 }
208             }
209         }
210     }
211 }
212 
                                                                    194,6-24      96%                                                                                                                                           183,8-32      81%

回显模式:



总结:

epoll对比之前的select和poll都有不小的改进,不用遍历整个buf,没有大小限制,并且有不同的模式可以选择,效率之高可想而知。






时间: 2024-10-18 12:12:41

高级I/O---多路复用---epoll的相关文章

I/O多路复用---epoll函数测试

参考文章来源: epoll使用详解(精髓) Epoll学习笔记 epoll是直到Linux2.6才出现了由内核直接支持的实现方法,那就是epoll,它几乎具备了之前所说的一切优点,被公认为Linux2.6下性能最好的多路I/O就绪通知方法. epoll可以同时支持水平触发和边缘触发(Edge Triggered,只告诉进程哪些文件描述符刚刚变为就绪状态,它只说一遍,如果我们没有采取行动,那么它将不会再次告知,这种方式称为边缘触发),理论上边缘触发的性能要更高一些,但是代码实现相当复杂. epol

IO多路复用--epoll详解

epoll 或者 kqueue 的原理是什么? [转自知乎] Epoll 引入简介 首先我们来定义流的概念,一个流可以是文件,socket,pipe等等可以进行I/O操作的内核对象. 不管是文件,还是套接字,还是管道,我们都可以把他们看作流.之后我们来讨论I/O的操作,通过read,我们可以从流中读入数据:通过write,我们可以往流写入数据.现在假定一个情形,我们需要从流中读数据,但是流中还没有数据,(典型的例子为,客户端要从socket读如数据,但是服务器还没有把数据传回来),这时候该怎么办

多路复用——epoll

1.基本知识 epoll是在2.6内核中提出的,是之前的select和poll的增强版本.相对于select和poll来说,epoll更加灵活,没有描述符限制.epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次. 2.epoll函数 epoll操作过程需要三个接口,分别如下: #include <sys/epoll.h>int epoll_create(int size);int epoll_ctl(in

I/O多路复用——epoll函数

1 select的低效率 select/poll函数效率比较低,主要有以下两个原因: (1)调用select函数后需要对所有文件描述符进行循环查找 (2)每次调用select函数时都需要向该函数传递监视对象信息 在这两个原因中,第二个原因是主要原因:每次调用select函数时,应用程序都要将所有文件描述符传递给操作系统,这给程序带来很大的负担.在高并发的环境下,无论怎样优化应用程序的代码,都无法完成应用的服务. 所以,select与poll并不适合以Web服务器端开发为主流的现代开发环境,只在要

I/O多路复用——epoll

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

IO多路复用--epoll

epoll就是为了 处理大批量句柄而改进的poll,相比与select,poll最大的好处在于它不会随着坚挺fd的数目增长而效率降低.因为在内核中的select是采用轮询来处理的,轮询fd的数目越多,自然耗时越多,并且slelct的监听数目有限(虽然可以通过头文件来改变,但并不治本) 一.epoll的相关系统调用 epoll只有三个简单地接口 分别为epoll_creat,epoll_ctl,epoll_wait (1)int epoll_creat(int size) 创建一个epoll句柄,

内核源码IO多路复用EPOLL

1. 简介: 本文将介绍内核epoll实现的原理.基于kernel 2.6.32版本. 本文只描述epoll对其他fd的监听,由于epoll本身也是一种文件系统,也可以被监听,这一部分不在这里介绍. 2. 基础数据结构: epoll中主要数据结构有两个,一个是epoll_create创建的epoll_fd的结构体eventpoll,一个是事件源对应的epitem结构体. epollevent的数据结构及相应解释如下:这里注意的是eventpoll中其实有两个ready list,一个是常规的rd

IO多路复用--epoll(待学习)

华清直播:http://www.makeru.com.cn/live/5413_1937.html 深度理解select.poll和epoll Linux IO模式及 select.poll.epoll详解 原文地址:https://www.cnblogs.com/y4247464/p/12237880.html

编写案例分别使用多进程、多路复用(select、epoll)实现tcp服务

-------------------------------多进程的tcp服务器-------------------------------通过为每个客户创建一个进程的方式,能够同时为多个客户进行服务器当客户不是特别多的时候,这种方式还行,如果有几百上千个,就不可取了,因为每次创建进程等过程需要好较大的资源 python代码案例: 1 #coding=utf-8 2 3 #引用对应的包 4 from socket import * 5 6 from multiprocessing impor

IO多路复用之epoll

1.基本知识 epoll是在2.6内核中提出的,是之前的select和poll的增强版本.相对于select和poll来说,epoll更加灵活,没有描述符限制.epoll使用一个文件描述符管理多个描述符,将用户关系的文件描述符的事件存放到内核的一个事件表中,这样在用户空间和内核空间的copy只需一次. 2.epoll接口 epoll操作过程需要三个接口,分别如下: #include <sys/epoll.h> int epoll_create(int size); int epoll_ctl(