多路复用之epoll服务器应用

来源:互联网 发布:python xpath解析html 编辑:程序博客网 时间:2024/05/22 17:30

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_ADDEPOLL_CTL_MODEPOLL_CTL_DELstruct 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 10240void 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模式下工作的套接字为非阻塞套接字。所以要注意当时间就绪时,要一次性把数据都读取完。

#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 4096void 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

这里写图片描述

cilent

这里写图片描述