Linux0

linux select poll epoll simple study

This project is maintained by wangfakang

linux的I/O复用技术

关于select poll epoll的讲解如下:     

一.select函数编程

int select(int nfds,fd_set*readfd,fd_set*writefd,fd_set*expectd,struct timeval *timeout);

函数参数:      

返回值:

一:当为-1的时候表示出错     
二:当为0的时候表示超时    
三:成功的话是一个大于0的整数    

注意:最多表示1024个集合(32个long型的 即:long bits[32];一共1024位)

select函数的应用:

主要运用与并发式的服务器端编程

其基本思路是: 首先是把要检测的文件描述符加载到fd_set类型的集合中,然后调用select函数检测加载到集合中的文件

描述符是否有(可读,可写,特殊异常)信息,再然后调用FD_ISSET函数判断到底是哪一个文件描述符变成了(可读,可写,特 殊异常)的了。

注意:
(在每次调用select之前都要对fdset集合进行FD_ZERO(&fdset)操作);进行清零

总结:
虽然多线程还有多进程都可以实现并发式服务器端编程,但是相对于select来说其花费的代价太高了,说白了select是以空 间来换取时间的,当然select实现的并发式服务器端编程也是有缺点的,原因是:由于 假设你只有两个客户端在和服务器端交互, 但是你每次都得遍历整个数组(或是链表)来寻找其相应的文件描述符

select的几大缺点:

其服务器端代码设计如下:

    struct timeval tv;
    int fds[MAXSIZE];
    memset(fds,-1,sizeof(fds));
    fd_set fdset;

    fds[0] = sockfd;

    while( 1 )
    {
        FD_ZERO(&fdset);
        int i = 0;
        int fdmax = fds[0];
       for( ; i < MAXSIZE; i++ )
        {
            if ( fds[i] != -1 )
            {
                FD_SET(fds[i],&fdset);
                if ( fdmax < fds[i] )
                {
                    fdmax = fds[i];
                }
            }
        }         
        tv.tv_sec = 2;
        tv.tv_usec = 0;
        int res = select( fdmax+1,&fdset,NULL,NULL,&tv);
        assert( res != -1 );
        if ( res == 0 )
        {
            printf("timeout\n");
        }
        else
        {
            int i = 0; 
            for( ; i < MAXSIZE ; i++ )
            {
                if ( fds[i] == -1 )
                {
                    continue;
                }
     if ( FD_ISSET( fds[i], &fdset) )
    {
        if ( fds[i] == sockfd )
        {
         int c = accept(sockfd,(struct sockaddr*)&caddr,&len);
           if ( c >= 0 )
            {
          if ( addfd(c,fds) == 0 ) //把c加载到数组中去
           {
               close(c);
              }                
            }
     }
      else
    {
      char buff[256]= {0};
      int n = read( fds[i],buff,255);
       if ( n  > 0 )
       {
       printf("read:%s\n",buff);
       write(fds[i],"OK",2);
        }
         else if( n == 0 )
          {
            delfd(fds[i],fds);
         } 

   }
   }
  }


 }

二.poll函数编程

#include<poll.h>

typedef unsigned long int nfds_t;


int poll(struct pollfd* fds,nfds_t nfds,int timeout);

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

int poll(struct pollfd* fds,nfds_t nfds,int timeout);

函数参数:   

返回值:
当为-1的时候表示失败,当为0的时候表示超时,当为大于0的整数的时候表示执行成功,表示文件描述符的个数

poll的编程实例:

struct pollfd fds[10]={0};
fds_init(fds);
fds_add(fd,fds,POLLIN);

while(1)
{
    int n=poll(fds,10,-1);
    assert(-1!=n);
    if(fds[i].revents &POLLRDHUP)
    {
         fds_del(fds[i],fds);
         continue;
    }
    int i=0;

    for(;i<10;++i)
   {
     if(fds[i].revents &POLLIN)
    {
        if(fds[i].fd ==fd)
       {
       int c=accept(fd,(struct sockaddr*)&ca,&len);
           if(c>0)
       {
       fds_add(c,fds,POLLIN|POLLRDHUP);
        }       
        }

        else
    {
        char buff[128]={0};
   进行读写操作

        }
    }

}

}

总结:
poll的特点:
首先和select的区别是:没有了最大文件描述符范围的规定了,而且和select相比,不再需要三个fd_set 的指针,只需要使 用一个 struct pollfd的指针即可

在然后就是由于每次调用select的时候在返回的时候都会对fdset集合进行修改,所以每次调用select的时候都要对fdset集合 进行FD_ZERO(),然后在把其文件描述加载到fdset集合中去,再然后调用select(即:监听我所设置文件描述符的所有相应 读事件),poll在这点上做了一定的优化采用了struct pollfd

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

events来告诉poll监听fd上的那些事件,而revents是内核修改的,用来通知应用程序fd上实际发生的事件,从而避免了反复的 对集合的清零与重置

三.epoll的编程

#include<sys/epoll.h>
int epoll_create(int size);    

参数一:size 并不起作用,只是给内核一个提示,说创建多大一个事件表
返回值:表示事件表的文件描述符

int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event)
struct  epoll_event
{
   int events;
   epoll_data_t data;//是一个union

};

返回值:成功为0 失败-1

int epoll_wait(int epfd,struct epoll_event *events ,int maxevents,int timeout)

参数二:当有相应的事件发生时,会把事件表中的放到events当中去

返回值:当为-1的时候表示失败,当为0的时候表示超时,当为大于0的整数的时候表示执行成功,表示文件描述符的个数

epoll的特点:

首先epoll达到了O(1)的时间复杂度,epoll把用户关心的文件描述符和想要监听的事件放在了内核的一张事件表中,而不必 要每次都要传入(文件描述符集和事件集)这样就不像select与poll那样每次都要把文件描述从用户态考到内核态(相对提高 了效率,实际上是通过共享内存实现的),之所以在达到了O(1)的时间复杂度是因为:在调用epoll_wait函数的时候只会把 就绪的事件从事件表中考到events当中去(其实是通过回调函数自己实现的)

其实epoll所采用就是一种分治法的思想,达到了各层的相互独立,比如使用epoll_create来创建事件表从而避免了select与poll 的缺点(每次都要从用户空间把相应的文件描述符拷到内核空间去)

注意:有关epoll的两种模式(LT ET)

注意:
   有时候epoll不一定比select和poll的效率高,比如这样的场景下:当活动连接数比较高的时候此时epoll会经常触发回调函数 ,此时在性能上还是有一定的损失.epoll适用于连接数量多,但是活跃的连接少.

   

欢迎一起交流学习

在使用中有任何问题,欢迎反馈给我,可以用以下联系方式跟我交流

Thx

Author