epoll

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服务器的步骤

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的返回值为就绪事件的个数,所以上层每一次遍历都为有效遍历。
缺点
1 代码书写复杂,难以调试。

现象

server

这里写图片描述

cilent

这里写图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值