linux epoll,poll,select

epoll函數用法,還有點poll和select

1,LT的epoll是select和poll函數的改進版。

特色是,讀完緩衝區後,若是緩衝區還有內容的話,epoll_wait函數還會返回,直到把緩衝區所有讀完。c++

2,ET的epoll(阻塞)

特色是,讀完緩衝區後,無論緩衝區還有沒有內容,epoll_wait函數都不會再返回,直到對端再一次發送信息過來。估計有的讀者朋友會想到用while去讀,可是有個致命的問題,由於文件描述符是阻塞的,因此當所有讀完後,進程就會阻塞在recv函數那裏,就不可以再處理別的鏈接了。windows

3,ET的epoll(非阻塞),效率最高的使用方法。

特色是,讀完緩衝區後,無論緩衝區還有沒有內容,epoll_wait函數都不會再返回,直到對端再一次發送信息過來。可是能夠事先用fcntl把文件描述符設置成非阻塞的方式,讓後用while一直去讀,當所有讀完後,recv函數也不會阻塞。數組

ET的epoll(非阻塞)的例子:微信

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

int main(int argc, char** argv){

  int port = atoi(argv[1]);
  int lfd = socket(AF_INET, SOCK_STREAM, 0);

  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);
  addr.sin_addr.s_addr = INADDR_ANY;

  bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
  listen(lfd, 5);

  int efd = epoll_create(10);

  struct epoll_event re;
  re.events = EPOLLIN;
  re.data.fd = lfd;

  epoll_ctl(efd, EPOLL_CTL_ADD, lfd, &re);

  struct epoll_event events[100];
  
  while(1){
    int ret = epoll_wait(efd, events, 100, -1);
    printf("======================wait=======\n");
    if(ret == -1){
      perror("epoll_wait");
      exit(1);
    }

    for(int i = 0; i < ret; ++i){
      if(events[i].data.fd == lfd){
        int cfd = accept(lfd, NULL, NULL);

        int flags = fcntl(cfd, F_GETFL);
        flags |= O_NONBLOCK;
        fcntl(cfd, F_SETFL, flags);
    
        struct epoll_event re;
        re.events = EPOLLIN | EPOLLET;
        re.data.fd = cfd;
        epoll_ctl(efd, EPOLL_CTL_ADD, cfd, &re);
        break;
      }
      char buf[3];

      int ret;
      while((ret = recv(events[i].data.fd, buf, sizeof buf, 0)) > 0){
        write(STDOUT_FILENO, buf, ret);
      }
      
      if(ret == 0){
        epoll_ctl(efd, EPOLL_CTL_DEL, events[i].data.fd, NULL);
        close(events[i].data.fd);
        printf("client disconnet\n");
      }
      else if(ret == -1 && errno == EAGAIN){
        printf("read over\n");  
      }
    }
  }
}

poll函數例子:socket

#include <stdio.h>
#include <poll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>

int main(int argc, char** argv){

  int port = atoi(argv[1]);
  int lfd = socket(AF_INET, SOCK_STREAM, 0);

  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_port = htons(port);
  addr.sin_addr.s_addr = INADDR_ANY;

  bind(lfd, (struct sockaddr*)&addr, sizeof(addr));
  listen(lfd, 5);

  struct pollfd pfd[1024];
  for(int i = 0; i < 1024; ++i){
    pfd[i].fd = -1;
  }
  pfd[0].fd = lfd;
  pfd[0].events = POLLIN;
  nfds_t maxfd = 0;
  
  while(1){
    int ret = poll(pfd, maxfd + 1, -1);
    printf("--------------poll------\n");
    if(pfd[0].revents & POLLIN){
      int cfd = accept(lfd, NULL, NULL);
      for(int i = 0; i < 1024; ++i){
        if(pfd[i].fd == -1){
          pfd[i].fd = cfd;
          pfd[i].events = POLLIN;
          maxfd++;
          break;
        }
      }
      continue;
    }

    for(int i = 0; i <= maxfd; ++i){
      if(pfd[i].revents & POLLIN){
        char buf[64];
        int ret = recv(pfd[i].fd, buf, sizeof buf, 0);
        if(ret == 0){
          pfd[i].fd = -1;
          close(pfd[i].fd);
          printf("client is disconnet\n");
        }
        else{
          write(STDOUT_FILENO, buf, ret);
        }
      }
    } 
    
  }
}

經過對比epoll和poll的例子能夠看出來:

  • epoll不須要事先決定數組的大小。poll須要。
  • epoll內部是用紅黑樹實現的效率,不會隨着鏈接的增多,而明顯的變低。poll是用鏈表實現的,因此性能隨着鏈接的增多而下降。poll還不能在windows下使用。epoll是跨平臺的。
  • 順便說下,select是用數組實現的,數組的大小由內核代碼寫死了,就是1024,因此想增大,只能從新編譯內核。可是select是在跨平臺的。

關於EPOLLOUT的補足:內核檢查寫的緩衝區,若是寫緩衝區未滿,處於可寫的狀態,epoll_wait函數就會返回。不然阻塞。

  • 水平模式:若是寫緩衝區未滿,epoll_wait會一直返回。
  • 邊緣模式:epoll_wait會先返回一次;而後,寫緩衝區從滿的狀態變成了未滿的狀態,epoll_wait返回。
    -注意點:調用send等函數的時候,若是寫緩衝區滿了的話,套接字若是是阻塞的,程序就費了,再也不能相應任何事件。若是是非阻塞的話,send就會失敗,有些數據就丟失了。因此,正確的作法是,當監聽到EPOLLIN事件的時候,把數據讀出來後,不要直接調用send等函數,要:把當前節點從樹上刪掉,而後加入一個EPOLLOUT的節點上去,等待epoll_wait的下一次返回,epoll_wait返回了,說明確定可寫。

select函數例子

#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <unistd.h>

int main(){

  int fd = socket(AF_INET, SOCK_STREAM, 0);
  
  struct sockaddr_in addr;
  addr.sin_family = AF_INET;
  addr.sin_addr.s_addr = INADDR_ANY;
  addr.sin_port = htons(12345);
  bind(fd, (struct sockaddr*)&addr, sizeof(addr));

  listen(fd, 5);

  fd_set readers, temp;
  FD_ZERO(&readers);
  FD_ZERO(&temp);

  FD_SET(fd, &readers);

  int maxfd = fd;

  int selret = 0;

  char rbuf[1024] = {0};
  while(1){
    temp = readers;
    selret = select(maxfd + 1, &temp, NULL, NULL, NULL); 

    
    if(FD_ISSET(fd, &temp)){
      //server
      int cfd = accept(fd, NULL, 0);
      maxfd = cfd;
      FD_SET(cfd, &readers);
      maxfd = maxfd < cfd ? cfd : maxfd;
      continue;
    }

    //client
    for(int i = fd + 1; i <= maxfd; ++i){
      if(FD_ISSET(i, &temp)){
    
    int ret = read(i, rbuf, sizeof(rbuf));
    printf("recv:%s\n", rbuf);
    if(ret == 0){
      FD_CLR(i, &readers);
    }
    ret = write(i, rbuf, sizeof(rbuf));
      }
    }
  }

}

c/c++ 學習互助QQ羣:877684253

本人微信:xiaoshitou5854

相關文章
相關標籤/搜索