zoukankan      html  css  js  c++  java
  • Select 、Poll 和 Epoll

    作用

    Epoll 和 Select 的作用都是为了多I/O同步复用的问题,利用Epoll、Poll或Select函数指定内核监听多个I/O的读、写、异常事件,避免每一个I/O都指定一个处理线程,导致开销过大。

    Select

    需要指定最大监听描述符的值,还要传入对应监听事件的fd_set(readwriteexception),每一次Select函数触发都会将集合内容修改,所以开发者要设置缓存区来记录所有文件描述符,每次调用函数的时候都要重新初始化监听集合。

    在Select触发后开发者需要去将缓存区的文件描述符与监听集合去匹配,例如描述符100在read set中,说明100有数据写入,可以立刻去读取,然后丢给worker线程去处理:

    int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
    
    #include <stdio.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <wait.h>
    #include <signal.h>
    #include <errno.h>
    #include <sys/select.h>
    #include <sys/time.h>
    #include <unistd.h>
    
    #define MAXBUF 256
    
    void child_process(void)
    {
      sleep(2);
      char msg[MAXBUF];
      struct sockaddr_in addr = {0};
      int n, sockfd,num=1;
      srandom(getpid());
      /* Create socket and connect to server */
      sockfd = socket(AF_INET, SOCK_STREAM, 0);
      addr.sin_family = AF_INET;
      addr.sin_port = htons(2000);
      addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
      connect(sockfd, (struct sockaddr*)&addr, sizeof(addr));
    
      printf("child {%d} connected 
    ", getpid());
      while(1){
            int sl = (random() % 10 ) +  1;
            num++;
         	sleep(sl);
      	sprintf (msg, "Test message %d from client %d", num, getpid());
      	n = write(sockfd, msg, strlen(msg));	/* Send message */
      }
    
    }
    
    int main()
    {
      char buffer[MAXBUF];
      int fds[5];
      struct sockaddr_in addr;
      struct sockaddr_in client;
      int addrlen, n,i,max=0;;
      int sockfd, commfd;
      fd_set rset;
      for(i=0;i<5;i++)
      {
      	if(fork() == 0)
      	{
      		child_process();
      		exit(0);
      	}
      }
    
      sockfd = socket(AF_INET, SOCK_STREAM, 0);
      memset(&addr, 0, sizeof (addr));
      addr.sin_family = AF_INET;
      addr.sin_port = htons(2000);
      addr.sin_addr.s_addr = INADDR_ANY;
      bind(sockfd,(struct sockaddr*)&addr ,sizeof(addr));
      listen (sockfd, 5); 
    
      for (i=0;i<5;i++) 
      {
        memset(&client, 0, sizeof (client));
        addrlen = sizeof(client);
        fds[i] = accept(sockfd,(struct sockaddr*)&client, &addrlen);
        if(fds[i] > max)
        	max = fds[i];
      }
      
      while(1){
    	FD_ZERO(&rset);
      	for (i = 0; i< 5; i++ ) {
      		FD_SET(fds[i],&rset);
      	}
    
       	puts("round again");
    	select(max+1, &rset, NULL, NULL, NULL);
    
    	for(i=0;i<5;i++) {
    		if (FD_ISSET(fds[i], &rset)){
    			memset(buffer,0,MAXBUF);
    			read(fds[i], buffer, MAXBUF);
    			puts(buffer);
    		}
    	}	
      }
      return 0;
    }
    

    Select 要求开发者去统计最大监听范围,fd_set是一个长度为32的整数数组,每一个fd对应一个bit位。内核会遍历传入集合的每一bit到最大描述符对应的bit。同时用户进程在处理返回结果的时候仍然要遍历集合,在用户进程中处理事件的效率是O(n)。它主要的优点是跨平台比较方便windows和linux都支持该函数。

    同时需要注意的是,如果正被select监听的文件描述符在另一个线程被关闭,实际上这个文件描述符不会被真正的关闭。

    Select 在处理超时精度上要比Epoll和Poll高,Epoll和Poll只能处理一毫秒的精度[4]。

    Poll

    在使用的时候要去传入一个pollfd数组,其中包含每一个需要监听的文件描述符。pollfd中包含监听事件和返回事件,不需要每次都去构建。

    struct pollfd {
          int fd;
          short events; 
          short revents;
    };
    

    与Select类似,用户在处理返回结果的时候要轮寻检查revents,看看是否和期待的事件一致。在内核中也需要遍历文件描述符的列表来找到受监视的对象,然后再遍历文件描述符列表去设置事件。它也具备Select的缺陷,即无法在其他线程中关闭正在监听的文件描述符[4]:

    int poll (struct pollfd *fds, unsigned int nfds, int timeout);
    
    for (i=0;i<5;i++) 
    {
    memset(&client, 0, sizeof (client));
    addrlen = sizeof(client);
    pollfds[i].fd = accept(sockfd,(struct sockaddr*)&client, &addrlen);
    pollfds[i].events = POLLIN;
    }
    sleep(1);
    while(1){
      puts("round again");
    poll(pollfds, 5, 50000);
    
    for(i=0;i<5;i++) {
    	if (pollfds[i].revents & POLLIN){
    		pollfds[i].revents = 0;
    		memset(buffer,0,MAXBUF);
    		read(pollfds[i].fd, buffer, MAXBUF);
    		puts(buffer);
    	}
    }
    }
    

    Poll 与 Select相比:

    • 不需要统计最大监听范围
    • 更适合大值的文件描述符监听
    • 文件描述符监听集合不用每次都重建,但仍需要自己维护
    • Select移植性更好,超时监听更加灵敏

    Epoll

    epoll 会在内核中帮我们创建一个context用于记录要监听的文件描述符,可以在等待I/O事件的同时去添加或移除文件描述符。每次只会返回就绪的文件描述符,用户进程在处理返回结果上更加方便,时间复杂度为O(1)。但由于Epoll只在linux中支持,不支持跨平台,也是最晚出现的方法。

      struct epoll_event events[5];
      int epfd = epoll_create(10); //指定监听数量,只是建议内核初始化分配空间,并不影响监听数量上限。
      ...
      ...
      for (i=0;i<5;i++) 
      {
        static struct epoll_event ev;
        memset(&client, 0, sizeof (client));
        addrlen = sizeof(client);
        ev.data.fd = accept(sockfd,(struct sockaddr*)&client, &addrlen);
        ev.events = EPOLLIN;
        epoll_ctl(epfd, EPOLL_CTL_ADD, ev.data.fd, &ev); 
      }
      
      while(1){
      	puts("round again");
      	nfds = epoll_wait(epfd, events, 5, 10000);
    	
    	for(i=0;i<nfds;i++) {
    			memset(buffer,0,MAXBUF);
    			read(events[i].data.fd, buffer, MAXBUF);
    			puts(buffer);
    	}
      }
    

    因为监听的描述符记录在了内核中,所以即使用户进程被关闭了,内核也会去监视对应的对象,可以用于边缘触发等场景。

    Epoll vs Select

    • 可以在等待的时候添加或移除监听对象
    • 只返回准备就绪的文件描述符
    • 用户进程处理事件效率更高(Epoll是O(1),Select是O(n))
    • Epoll只在Linux中支持,移植性差
    • Select超时监听更加灵敏
    • Epoll使用起来更复杂

    Epoll vs Poll

    • 可以在等待的时候添加或移除监听对象
    • 只返回准备就绪的文件描述符
    • Epoll使用起来更复杂
    • 用户进程处理事件效率更高(Epoll是O(1),Poll是O(n))
    • Epoll使用起来更复杂

    总结

    Select、Epoll、Poll都是I/O多路复用的一种技术,都属于同步I/O的范畴,用户进程同时监听多个I/O事件[2]。

    Select与Poll比较类似,每次调用函数都需要将监听的集合传入内核中,即要copy一次,返回的结果要轮寻匹配。不过相较Select而言Poll没有最大监听范围限制(Select在linux中最大监听范围为1024,此值可以修改),在没有变动的情况下无需每次调用时重新构建监听集合,而Select在跨平台性上和处理超时精度上是三者中最优的[4]。

    Epoll是Select和Poll的优化版,它采用一个内核context来维护监听集合,如果在不需要变动的情况下,监听描述符设置只需要从用户进程向内核中copy一次。但读者要注意,每调用epoll_ctl函数去更新监听描述符的时候都是一次copy,由于不支持批量操作,每一个描述符都需要执行一次copy,所以如果是频繁的短链接,epoll的效率未必比poll好[4]。每次直接返回准备态的文件描述符集合,不需要轮寻匹配,用户进程处理事件的效率是最高的。

    参考地址

  • 相关阅读:
    LAPACK(5)——矩阵广义特征值问题和QZ分解
    数据结构与算法——堆
    STL(1)——查找函数find的使用
    数据结构与算法——多项式
    LAPACK(6)——总结
    设计模式代理模式
    C#防盗链
    设计模式组合模式
    JavascriptFolder对象
    JavascriptTextStream对象
  • 原文地址:https://www.cnblogs.com/cnblogs-wangzhipeng/p/9596801.html
Copyright © 2011-2022 走看看