欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

epoll

程序员文章站 2022-06-13 22:46:51
...

I/O 多路复用

I/O 多路复用主要是为了解决同时要等待多个文件描述符而导致的错误,它一般的使用场景

  1. 处理多个文件描述符(同时处理 一个 socket 或者 标准输入/输出)
  2. 处理多个socket (Server)
  3. 一个Server同时处理tcp 和 udp模块(一个端口号即绑定tcp 又绑定 udp)
  4. 处理多种服务或者协议(inetd)

它一般用于不帮我们直接 I/O,它只是帮我们通知事件就绪,对于tcp 和 udp的就绪条件如下

  1. udp一个完整的数据报到达
  2. tcp 模块收到的数据已经到达了低水位线

epoll的三个系统调用

int epoll_create(int size);

    它会创建一个epoll模型在内存中,创建成功后返回该模型的文件描述符。size表最多能注册多少个文件描述符,在Linux 2.6.8 版本之后不在关心。

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

epfd :epoll模型的文件描述符。
op :  指将那个文件描述符,在红黑树中进行增、删、改的那个操作。
fd:指要操作的特定的文件描述符。
event:指特定文件描述符所关心的事件。

int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);
epfd  : epoll 模型的文件描述符。
events: epoll_event结构体数组的地址。
maxevents:数组大小
timeout:等待超时的时间设置,单位为毫秒。当设为-1代表阻塞,0代表非阻塞,大于0代表轮询。
op参数
EPOLL_CTL_ADD
EPOLL_CTL_MOD
EPOLL_CTL_DEL

struct event_event结构体 

struct epoll_event {
      __uint32_t   events;      /* Epoll events */
      epoll_data_t data;        /* User data variable ,大小为8字节是为了64位下指针为8字节*/
  };

 typedef union epoll_data {
     void        *ptr;
     int          fd;
     __uint32_t   u32;
     __uint64_t   u64;
  } epoll_data_t;

具体的模型:
epoll

写epoll服务器的步骤

step 1 :
创建出来一个listen_sock
具体为:
socket调用
setsockopt调用(设置后该socket的端口号,可被该主机的其他ip地址绑定,设置后服务器主动断开不再time_wait等待)
fcntl调用(可选项 LT模式可不调用,ET模式必须把该套接字调用设为非阻塞)
bind调用
listen调用

step 2:
创建一个epoll模型,epoll_create调用
将listen_sock调用epoll_ctl,把监听套接字加入到红黑树中,并关心listen_sock的读事件。
调用epoll_wait,等待时间就绪
设置 switch case 分支语句

step 3:
switch case {
-1:
表调用失败
0:
表超时
default:
表等待成功
for 循环遍历整个传入的event数组,遍历每个fd就绪事件,进行处理
}

step 4:
遍历整个数组的逻辑:
if(fd是监听套接字){
accept 调用 (LT这里为if 确保不会被阻塞服务器进程,ET这里为while需遍历完所有数据以防被下次到来的数据覆盖)
将accept 的读事件先注册进红黑树(调用epoll_ctl)
}
else if(其他fd读事件就绪){
进行数据读取,并修改该套接字,将该套接字的写时间也注册进红黑树,一般只要该进程在跑,write事件立刻就绪。
}
else{
这里就是所有文件描述符的写事件了,
这里可以写我们自己想回复的数据
}

LT 模式

LT 模式
  只要底层有数据,它会不断通知调用者去取走数据。只要数据没有被取走它会一直把相应事件标志为事件就绪。epoll默认为LT模式,我们可在epoll_ctl中设置为ET模式。

#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <stdlib.h>
#define SIZE 10240
void usege(const char * arg)
{
   printf("%s   ip  port\n",arg);
}
typedef struct epoll_buff{
int fd;      //这个结构体很重要如果没有的话,数据就混乱了读的时候数据全读到buff中了,write的时候这个
char buf [SIZE];//buff 不知道到底是对应的是那个文件描述符
} epoll_buf,*epoll_buf_p;
epoll_buf_p alloc(int fd) 
{
      size_t len=sizeof(struct epoll_buff);
 //     printf("size:%d\n",len);
      epoll_buf_p ret =(epoll_buf_p) malloc(len);
      if (ret ==NULL) {
      perror("malloc");  
       exit(10);
      }
      ret->fd=fd;
      return ret;
}
int startup(const char * ip,const char * port)
{
    int fd = socket(AF_INET,SOCK_STREAM,0);
    if(fd<0) {
    perror("socket");
    exit(1);
   }
   int opt = 1;
   setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
   struct sockaddr_in sock_addr;
   sock_addr.sin_family=AF_INET;
   sock_addr.sin_port = htons(atoi(port));
   sock_addr.sin_addr.s_addr=inet_addr(ip);
   socklen_t len=sizeof(sock_addr);
   if((bind(fd,(struct sockaddr*)&sock_addr,len))<0)
   {
     perror("bind");
     exit(2);
   }
   if(listen(fd,128)<0)
   {
     perror("listen");
     exit(3);
   }
   return fd;
}

int main(int argv,const char * args[])
{
   if(argv != 3)
   {
     usege(args[0]);
     exit(4);
   }
   int listen_sock=startup(args[1],args[2]);
   int epfd=epoll_create(256);
 //  printf("lsock%d , epfd%d\n",listen_sock,epfd);
   if(epfd<0)
   { perror("epoll_create");}
    struct epoll_event ev;
   struct epoll_event env[32];
   ev.events=EPOLLIN;
   ev.data.ptr=alloc(listen_sock);
   if(epoll_ctl(epfd, EPOLL_CTL_ADD,listen_sock,&ev)<0)
   {
     perror("epoll_ctl");
     exit(5);
   }
   while(1)
   {
     int timeout = -1;
     int reve_n = epoll_wait(epfd,env,sizeof(env)/sizeof(env[0]),timeout);
    // printf("reve_n :%d\n",reve_n);
     switch (reve_n)
    {
      case 0:
        printf("time out \n");
        break;
      case -1:
        perror("epoll_wait");
         exit(6);
      default:
   {   int idx=0;
       for(idx;idx<reve_n;idx++)
      {
       epoll_buf_p p=(epoll_buf_p)env[idx].data.ptr;
 //      printf("p->fd %d\n",p->fd);
       if(p->fd == listen_sock&&env[idx].events==EPOLLIN)
      { struct sockaddr_in cilent ;
        socklen_t len=sizeof(cilent);
        int sock=0;
        if((sock=accept(listen_sock,(struct sockaddr*)&cilent,&len))>0) // while ET
        {
             ev.events=EPOLLIN;
             ev.data.ptr=alloc(sock);
             if(epoll_ctl(epfd, EPOLL_CTL_ADD,sock,&ev)<0)
             {
                 perror("epoll_ctl");
                 exit(7);
             }
             const char * cilent_ip=inet_ntoa(cilent.sin_addr);
             int   cilent_port=ntohs(cilent.sin_port);
            printf("cilent connect ip:%s port:%u \n",cilent_ip,cilent_port);
       }
       if(sock<0) {
         perror("accept");
       }
      continue;
      }
      else if(env[idx].events==EPOLLIN)
      {
         int res=read(p->fd,p->buf,SIZE);
         if(res<0){
           perror("read");
           exit(8);
          }
         else if(res==0)
         {
           printf("cilent quit!!\n");
           close(p->fd);
           free(p);
           epoll_ctl(epfd,EPOLL_CTL_MOD,p->fd,NULL);
           }
         else {
       // if((p->buf)[res-2]=='\r') 
       // (p->buf)[res-2]=0;
      //  else {
       // (p->buf)[res-1]=0;
       // }
         (p->buf)[res]=0;
         printf("####cilent : %s",p->buf);
         fflush(stdout);
        }
        ev.events=(env[idx]).events|EPOLLOUT;
       if(epoll_ctl(epfd,EPOLL_CTL_MOD,p->fd,&ev)<0)
        {
          perror("epoll_ctl");
          exit(9);
        }
       } else{ // end read  
        const char * temp ="HTTP/1.1 200 OK\r\n Content-Length :%s \r\n\r\n wo xi huan yutian  !!! ";
        int ret= sprintf(p->buf,"%s",temp);
         write(p->fd,p->buf,ret);
         epoll_ctl(epfd,EPOLL_CTL_DEL,p->fd,NULL);
         close(p->fd);
        // ev.events=EPOLLIN;
       //  if(epoll_ctl(epfd,EPOLL_CTL_MOD,p->fd,&ev)<0)
      //  {
      //    perror("epoll_ctl");
     //     exit(9);
      //  }
       }
     }
    }
     break;
   }// end switch
 } // end while(1)
 close(epfd);
     return 0;
}

ET 模式

ET模式是epoll的高效模式,可以在每次进行epoll_ctl时进行设置为EPOLLET时,该文件描述符以ET模式工作。ET模式只在数据从无到有时通知一次,并且ET模式下工作的套接字应为非阻塞套接字。所以要注意当时间就绪时,要一次性把数据都读取完。
在ET模式下设置为非阻塞套接字十分重要,因为我们不知道下次是否还有数据没有,如果没有我们阻塞住了那么整个Server就阻塞住了。

#include<stdio.h>
#include<sys/socket.h>
#include<sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <stdlib.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>

#define SIZE 4096

void usege(const char * arg)
{
   printf("%s   ip  port\n",arg);
}
typedef struct epoll_buff{
int fd;
char buf [SIZE];
} epoll_buf,*epoll_buf_p;
epoll_buf_p alloc(int fd)
{
      size_t len=sizeof(struct epoll_buff);
 //     printf("size:%d\n",len);
      epoll_buf_p ret =(epoll_buf_p) malloc(len);
      if (ret ==NULL) {
      perror("malloc");
      exit(10);
      }
      ret->fd=fd;
      return ret;
}
static void set_noblock(int fd)
{
   int fd_flag = fcntl(fd,F_GETFL);
   if(fcntl(fd,F_SETFL,fd_flag|O_NONBLOCK)<0)
   {
     perror("fcntl");
     exit(11);
   }
}
void Read(epoll_buf_p p,int epfd, struct epoll_event * ev_arr)
{
    while((res=read(p->fd,p->buf,SIZE))>0)
    {
       (p->buf)[res]=0;
       printf("#### cilent : %s",p->buf);
       fflush(stdout);
    }
    if(res==0)
    {
     printf("cilent quit");
     fflush(stdout);
     if(epoll_ctl(epfd,EPOLL_CTL_DEL,p->fd,NULL)<0)
     {
       perror("epoll_ctl");
       exit(12);
     }
     close(p->fd);
    }
    if(res<0&&errno!=EAGAIN)
    {
    perror("read");
    exit(13);
    }
    struct epoll_event ev;
    ev.events = ev_arr->events|EPOLLOUT; //这步是为了将关心的事件改为即关心读又关心写 
    ev.data.ptr=p;
    if(epoll_ctl(epfd,EPOLL_CTL_MOD,p->fd,&ev)<0)
    {
          perror("epoll_ctl");
    }
}
void Write(epoll_buf_p p,int epfd)
{
    const char * temp ="HTTP/1.1 200 OK\r\n Content-Length :%s \r\n\r\n wo xihuan yutian !!! ";
    int ret= sprintf(p->buf,"%s",temp);
   write(p->fd,p->buf,ret);
    epoll_ctl(epfd,EPOLL_CTL_DEL,p->fd,NULL);
    close(p->fd);
}
int startup(const char * ip,const char * port)
{
    int fd = socket(AF_INET,SOCK_STREAM,0);
    if(fd<0) {
    perror("socket");
    exit(1);
   }
   set_noblock(fd);
   int opt = 1;
   setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
   struct sockaddr_in sock_addr;
   sock_addr.sin_family=AF_INET;
   sock_addr.sin_port = htons(atoi(port));
   sock_addr.sin_addr.s_addr=inet_addr(ip);
   socklen_t len=sizeof(sock_addr);
   if((bind(fd,(struct sockaddr*)&sock_addr,len))<0)
   {
     perror("bind");
     exit(2);
   }
   if(listen(fd,128)<0)
   {
     perror("listen");
     exit(3);
   }
   return fd;
}

int main(int argv,const char * args[])
{
   if(argv != 3)
 {
     usege(args[0]);
     exit(4);
   }
   int listen_sock=startup(args[1],args[2]);
   int epfd=epoll_create(256);
 //  printf("lsock%d , epfd%d\n",listen_sock,epfd);
   if(epfd<0)
   { perror("epoll_create");}
   struct epoll_event ev;
   struct epoll_event env[32];
   ev.events=EPOLLIN|EPOLLET;
   ev.data.ptr=alloc(listen_sock);
   if(epoll_ctl(epfd, EPOLL_CTL_ADD,listen_sock,&ev)<0)
   {
     perror("epoll_ctl");
     exit(5);
   }
   while(1)
   {
     int timeout = -1;
     int reve_n = epoll_wait(epfd,env,sizeof(env)/sizeof(env[0]),timeout);
    // printf("reve_n :%d\n",reve_n);
     switch (reve_n)
    {
      case 0:
        printf("time out \n");
        break;
      case -1:
        perror("epoll_wait");
        exit(6);
      default:
   {   int idx=0;
       for(idx;idx<reve_n;idx++)
      {
       epoll_buf_p p=(epoll_buf_p)env[idx].data.ptr;
        if(p->fd == listen_sock&&env[idx].events&EPOLLIN)
      { struct sockaddr_in cilent ;
        socklen_t len=sizeof(cilent);
        int sock=0;
        while((sock=accept(listen_sock,(struct sockaddr*)&cilent,&len))>0) // while ET
        {
             set_noblock(sock);
             ev.events=EPOLLIN|EPOLLET;
             ev.data.ptr=alloc(sock);
             if(epoll_ctl(epfd, EPOLL_CTL_ADD,sock,&ev)<0)
             {
                 perror("epoll_ctl");
                 exit(7);
             }
             const char * cilent_ip=inet_ntoa(cilent.sin_addr);
              int   cilent_port=ntohs(cilent.sin_port);
            printf("cilent connect ip:%s port:%u \n",cilent_ip,cilent_port);
       }
       if(sock<0) {
         perror("accept");
       }
      continue;
      }
      else if(env[idx].events&EPOLLIN)
      {
         Read(p,epfd, env[idx]);
       }
     else{
        Write(p,epfd);
       }
     }
    }
     break;
   }// end switch
 } // end while(1)
   return 0;
}

epoll 服务器优缺点

优点
1 它其中所等待的文件描述符没有上限,具体极限跟硬件设备内存有关。
2 以高效的回调方式通知事件就绪,所以通知事件就绪的时间复杂度为O(1)
3 epoll模型,底层是以红黑树构成,增删查改很高效
4 以回调方式**就绪节点,很高效。
5 epoll_wait的返回值为就绪事件的个数,所以上层每一次遍历都为有效遍历。
6 使用内存映射技术,将就绪队列映射到event数组上,不需再次进行拷贝数据给上层event数组
缺点
1 代码书写复杂,难以调试。

现象

server

epoll

cilent

epoll