IO 多路转接 epoll

☆樱花仙子☆ 提交于 2020-02-20 01:15:28

read 函数返回值

    >0 :实际读到的字节数

    =0 :socket中,表示对端关闭 close()

    -1 :    如果errno = EINTR  被异常中断。需要重启。

        如果errno =EAGIN 或 EWOULDBLOCK 以非阻塞的方式读数据,但是没有数据。需要再次读

        如果errno = ECONNRESET 说明连接被重置。需要close()。 移除监听队列

        错误

突破 1024 文件描述符限制

        cat  /proc/sys/fs/file-max  

        当前计算机能打开的最大文件个数。受硬件影响

        ulimit -a  -->当前用户下的进程。默认打开文件件描述符个数  缺省 为 1024

        修改:

            打开 sudo vi /etc/security/limits.conf 写入

            *    soft nofile 65536    -->设置默认值 可以直接借助命令修改【注销用户 使其生效】
                
            *    hard  nofile 100000    --》命令修改上限


            命令修改: ulimit -n 21000      

突破 1024 文件描述符限制

        cat  /proc/sys/fs/file-max  

        当前计算机能打开的最大文件个数。受硬件影响

        ulimit -a  -->当前用户下的进程。默认打开文件件描述符个数  缺省 为 1024

        修改:

            打开 sudo vi /etc/security/limits.conf 写入

            *    soft nofile 65536    -->设置默认值 可以直接借助命令修改【注销用户 使其生效】
                
            *    hard  nofile 100000    --》命令修改上限


            命令修改: ulimit -n 21000      
    


epoll:


    int epoll_create(int size)   创建一颗监听红黑树

        size:创建的红黑树的监听结点数量(仅供内核参考)
    
        返回值: 指向新创建的红黑树的根结点 fd

            失败 -1 errno

    int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event) 操作监听红黑树

        epfd:epoll_create函数的返回值

        op:对该监听红黑树所做的操作

            EPOLL_CTL_ADD 添加fd到监听红黑树

            EPOLL_CTL_MOD 修改fd在 监听红黑树上的监听事件

            EPOLL_CTL_DEL 将一个fd 从监听红黑树上摘下(取消监听)

        fd:
            待监听的fd


        event: 本质 struct epoll_event 结构体地址 (传入参数)

            成员:

                events:

                    EPOLLIN/EPOLLOUT/EPOLLERR

                data:联合体

                    int fd 对应监听事件的fd

                    void* ptr

                    uint32_t u32

                    uint64_t u64

        返回值:成功:0  ; 失败 -1 errno

    
    int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout); 阻塞监听


        epfd:    epoll_create 函数返回值 

        events:传出参数【数组】,满足监听条件的 那些fd结构体

        maxevents:数组 元素的总个数 1024

            struct epoll_event events[1024]

        timeout:

            -1:阻塞

            0:不阻塞

            》0:超时时间(毫秒)

        返回值:

            》0:满足监听的 总个数。可以用作循环上限

            0 :没有fd满足监听事件

            -1 : 失败 errno


epoll 实现多路IO转接思路

    lfd = socket();   监听连接事件 lfd

    bind()

    listen()

    int epfd = epoll_create()    epfd,监听红黑树树根

    struct epoll_event tep,ep[1024]  ;  tep,用来设置单个fd属性,ep是 epoll_wait() 传出的满足监听事件的数组

    tep.events = EPOLLIN;            初始化lfd 监听属性
    tep.data.fd = lfd;


    epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&tep)        将lfd 添加到监听红黑树上

    while(1){


        ret = epoll_wait (epfd,ep,1024,-1);   实施监听

        for(i=0;i<ret;i++){

            if(ep[i].data.fd == lfd){    //lfd 满足读事件,有新的客户端发起连接请求
            
                cfd = accept();

                tep.events = EPOLLIN;    //初始化cfd监听属性
                tep.data.fd = cfd;

                epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&tep);

            }else{

            n = read(ep[i].data.fd,buf,sizeof(buf));

            if(n==0){

                close(ep[i].data.fd)
            
                epoll_ctl(epfd,EPOLL_CTL_DEL,ep[i].data.fd,NULL) //将关闭的cfd 从监听树上摘下


            }else if(n>0){

                小 --》大

                write(ep[i].data.fd,buf,n);

            }

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/epoll.h>

#define MAXLINE 8192
#define SERV_PORT 8000

#define OPEN_MAX 5000


int main(){
  int i,listenfd,connfd,sockfd;
  int n,num=0;
  ssize_t nready,efd,res;
  char buf[BUFSIZ],str[INET_ADDRSTRLEN];
  socklen_t clilen;

  struct sockaddr_in cliaddr,servaddr;
  struct epoll_event tep,ep[OPEN_MAX];

  listenfd = socket(AF_INET,SOCK_STREAM,0);
  int opt =1;
  setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//端口复用
  bzero(&servaddr,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(SERV_PORT);
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

  bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
  listen(listenfd,20);



  efd=epoll_create(OPEN_MAX);
  if(efd==-1){
    perror("epoll_create error");
    exit(1);

  }
  tep.events = EPOLLIN; tep.data.fd = listenfd;
  res = epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&tep); //将lfd及对应的结构体设置到树上,efd可以找到该树
  if(res ==-1){
    perror("epoll_ctl error");
    exit(1);
  }
  for(;;){
    
    nready = epoll_wait(efd,ep,OPEN_MAX,-1);
    if(nready==-1){
      perror("epoll_wait error");
      exit(1);
    }
    for(i=0;i<nready;++i){
      if(ep[i].data.fd==listenfd){
        clilen = sizeof(cliaddr);
        connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen);
        printf("received from %s  PORT %d\n",
            inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),
              ntohs(cliaddr.sin_port));
        printf("cfd %d--client %d\n",connfd,++num);
        tep.events = EPOLLIN;tep.data.fd = connfd;
        res = epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&tep);
        if(res == -1){
          perror("epoll_ctl error");
          exit(1);
        }
      }else {
        sockfd = ep[i].data.fd;
        n = read(sockfd,buf,MAXLINE);
        if(n==0){                   //读到0 说明客户端关闭连接
          res = epoll_ctl(efd,EPOLL_CTL_DEL,sockfd,NULL);//将该文件描述符从红黑树上摘除
          close(sockfd);            //关闭与客户端连接
          printf("client[%d]closed connection\n",sockfd);
        }else if(n<0){
          perror("read n<0 error");
          res = epoll_ctl(efd,EPOLL_CTL_DEL,sockfd,NULL);
          close(sockfd);
        }else{
          for(i= 0;i<n;i++)
            buf[i]=toupper(buf[i]);

          write(STDOUT_FILENO,buf,n);
          write(sockfd,buf,n);
        }
      }
    }



  }
  close(listenfd);
  close(efd);

  return 0;
}

 

epoll 事件模式

    ET 模式(边沿触发):

    
    高速工作方式,只支持 no-block  socket  在这种模式下,当描述符从未就绪变为就绪时,内核会通过 epoll 告诉你。

    然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个

    fd 做 IO 操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)
        

            缓冲区剩余未读尽的数据不会导致 epoll_wait 返回。 新的事件满足,才会触发。

            struct epoll_event  event;

            event.events = EPOLLIN | EPOLLET;


    LT模式:(水平触发 -- 默认采用模式)

    支持 block 和 no -block socket 。内核会告诉你一个文件描述符是否就绪了,然后你可以对就绪文件描述符 IO操作

    若果不做任何操作,内核还是后继续通知你,所以,这种编程模式出错误可能性小 传统的select poll都是这种

    

            缓冲区剩余未读尽数据会导致 epoll_wait 返回

//借助管道 了解 epoll ET /LT 模式
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/epoll.h>
#include<errno.h>
#include<unistd.h>

#define MAXLINE 10

int main(int argc,char* argv[]){
  int efd,i;
  int pfd[2];
  pid_t pid;
  char buf[MAXLINE],ch = 'a';

  pipe(pfd);
  pid = fork();

  if(pid==0){       //子写
    close(pfd[0]);
    while(1){
      //aaaa\n
      for(i=0;i<MAXLINE/2;++i)
        buf[i]=ch;
      buf[i-1]='\n';
      ch++;
      //bbbb\n
      for(;i<MAXLINE;++i)
        buf[i]=ch;
      buf[i-1]='\n';
      ch++;
      //aaaa\nbbbb\n
      write(pfd[1],buf,sizeof(buf));
      sleep(5);
    }
    close(pfd[1]);
  }else if(pid>0){      //父读
    struct epoll_event event,resevent[10];
    int res,len;
    close(pfd[1]);
    efd = epoll_create(10);
   event.events = EPOLLIN | EPOLLET;     //边沿触发
    //event.events = EPOLLIN ;    //水平触发(默认)
    event.data.fd = pfd[0];
    epoll_ctl(efd,EPOLL_CTL_ADD,pfd[0],&event);
    while(1){
      res= epoll_wait(efd,resevent,10,-1);
      printf("res %d\n",res);
      if(resevent[0].data.fd == pfd[0]){
        len = read(pfd[0],buf,MAXLINE/2);
        write(STDOUT_FILENO,buf,len);
      }
    }
    close(pfd[0]);
    close(efd);
    

  }

  return 0;
}
// 借助套接字 了解 epoll ET/LT模式


//server

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/epoll.h>

#define MAXLINE 10
#define SERV_PORT 9000



int main(){
  int i,listenfd,connfd,sockfd;
  int len;
  int n,num=0;
  ssize_t nready,efd,res;
  char buf[MAXLINE],str[INET_ADDRSTRLEN];
  socklen_t clilen;

  struct sockaddr_in cliaddr,servaddr;
  struct epoll_event event,resevent[10];

  listenfd = socket(AF_INET,SOCK_STREAM,0);
  int opt =1;
  setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//端口复用
  bzero(&servaddr,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(SERV_PORT);
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

  bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
  listen(listenfd,20);



  efd=epoll_create(10);
  event.events = EPOLLIN | EPOLLET; //边缘触发
  //event.events = EPOLLIN ;// 水平  clilen = sizeof(cliaddr);
  connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen);
  printf("received from %s  PORT %d\n",
      inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),
      ntohs(cliaddr.sin_port));
  printf("cfd %d--client %d\n",connfd,++num);
  event.data.fd = connfd;
  res = epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&event);
  while(1){
    res = epoll_wait (efd,resevent,10,-1);
    printf("res %d\n",res);
    if(resevent[0].data.fd == connfd){
      len = read (connfd,buf,MAXLINE/2);
      write(STDOUT_FILENO,buf,len);
    }
  }
  return 0;

}

================================================================================
//client

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>

#define SERV_PORT  9000
#define MAXLINE 10


void sys_err(const char* str){
  perror(str);
  exit(1);
}

int main(){
  int sockfd,i;
  char ch  = 'a';
 
  char buf[MAXLINE];
  struct sockaddr_in serv_addr;   //服务器地址结构

  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(SERV_PORT);

  inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr.s_addr);


  sockfd= socket(AF_INET,SOCK_STREAM,0);
  connect(sockfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    while(1){
      for(i=0;i<MAXLINE/2;++i)
        buf[i]= ch;
      buf[i-1]='\n';
      ch++;
      for(;i<MAXLINE;++i)
        buf[i]=ch;
      buf[i-1]='\n';
      ch++;
      write(sockfd,buf,sizeof(buf));
      sleep(5);
    }
    close(sockfd);


  return 0;
}

结论:

        epoll 的 ET 模式 高效模式 ,但是只支持 非阻塞模式  -- 忙轮询


        struct epoll_event event;

        event.events = EPOLLIN | EPOLLET:

        epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&event)

        int flag = fcntl(cfd,F_GETFL)

        flag|=0_NOBLOCK

        fcntl(cfd,F_SETFL,flag)

// epoll ET模式 非阻塞使用方式


//serve
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<sys/epoll.h>
#include<fcntl.h>

#define MAXLINE 10
#define SERV_PORT 9000



int main(){
  int i,listenfd,connfd,sockfd;
  int len,flag;
  int n,num=0;
  ssize_t nready,efd,res;
  char buf[MAXLINE],str[INET_ADDRSTRLEN];
  socklen_t clilen;

  struct sockaddr_in cliaddr,servaddr;
  struct epoll_event event,resevent[10];

  listenfd = socket(AF_INET,SOCK_STREAM,0);
  int opt =1;
  setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));//端口复用
  bzero(&servaddr,sizeof(servaddr));
  servaddr.sin_family = AF_INET;
  servaddr.sin_port = htons(SERV_PORT);
  servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

  bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
  listen(listenfd,20);



  efd=epoll_create(10);
  event.events = EPOLLIN | EPOLLET; //边缘触发
  clilen = sizeof(cliaddr);
  connfd = accept(listenfd,(struct sockaddr*)&cliaddr,&clilen);
  printf("received from %s  PORT %d\n",
      inet_ntop(AF_INET,&cliaddr.sin_addr,str,sizeof(str)),
      ntohs(cliaddr.sin_port));
  printf("cfd %d--client %d\n",connfd,++num);


  flag = fcntl(connfd,F_GETFL);
  flag |= O_NONBLOCK;
  fcntl(connfd,F_SETFL,flag);


  event.data.fd = connfd;
  res = epoll_ctl(efd,EPOLL_CTL_ADD,connfd,&event);
  while(1){
    res = epoll_wait (efd,resevent,10,-1);
    printf("res %d\n",res);
    if(resevent[0].data.fd == connfd){
      while((len = read (connfd,buf,MAXLINE/2))>0){  //非阻塞读 轮询
        write(STDOUT_FILENO,buf,len);
      }
    }

  }
  return 0;
}
==================================================================================
//client
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<ctype.h>

#define SERV_PORT  9000
#define MAXLINE 10


void sys_err(const char* str){
  perror(str);
  exit(1);
}

int main(){
  int sockfd,i;
  char ch  = 'a';
 
  char buf[MAXLINE];
  struct sockaddr_in serv_addr;   //服务器地址结构

  serv_addr.sin_family = AF_INET;
  serv_addr.sin_port = htons(SERV_PORT);

  inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr.s_addr);


  sockfd= socket(AF_INET,SOCK_STREAM,0);
  connect(sockfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
    while(1){
      for(i=0;i<MAXLINE/2;++i)
        buf[i]= ch;
      buf[i-1]='\n';
      ch++;
      for(;i<MAXLINE;++i)
        buf[i]=ch;
      buf[i-1]='\n';
      ch++;
      write(sockfd,buf,sizeof(buf));
      sleep(5);
    }
    close(sockfd);


  return 0;
}


    优点:

        高效 。 突破1024 文件描述符

        epoll是Linux下多路复用IO接口select/poll的增强版本,

        它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,

        因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,

        另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。

    缺点:

        不能跨平台

 

 


    epoll ET 模式 + 非阻塞、轮询+ void* ptr

    原来:socket、bind、1isten -- epol1_create 创建监听红黑树--返回epfd - -epol1_ct1()向树上添加一个监听fd - while (1) -- 

        epol1_wait 监听 -- 对应监听 fd 有事件产生 -- 返回监听满足数组。-- 判断返回数组元素 -- 1fd满足- Accept - cfd满足

        - read() ---小-->大-- write回去。

    反应堆:不但要监听 cfd的 读事件、还要监听cfd的写事件。

        ocket、bind、1isten -- epol1_create 创建监听红黑树--返回epfd - -epol1_ct1()向树上添加一个监听fd - while (1) -- 

        epol1_wait 监听 -- 对应监听 fd 有事件产生 -- 返回监听满足数组。-- 判断返回数组元素 -- 1fd满足- Accept - cfd满足

        - read() ---小-->大 --cfd从监听红黑树上摘下 --EPOLLOUT ---回调函数 -- epoll_ctl() --EPOLL_CTL_ADD 重新放到红黑树上

        监听写事件 -- 等待 epoll_wait 返回 -- 说明 cfd 可写 -- write回去 -- cfd从监听红黑树上摘下 --EPOLLIN --epoll_ctl()--

        EPOLL_CTL_ADD 重新放到红黑树上监听读事件  --epoll_wait 监听    

 


 

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!