Chinaunix首页 | 论坛 | 博客
  • 博客访问: 65427
  • 博文数量: 43
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 420
  • 用 户 组: 普通用户
  • 注册时间: 2014-06-27 15:04
个人简介

记录,分享

文章分类

全部博文(43)

文章存档

2017年(24)

2015年(1)

2014年(18)

我的朋友

分类: C/C++

2014-06-27 15:48:21

  select/poll的缺点在于:
1.每次调用时要重复地从用户态读入参数。
2.每次调用时要重复地扫描文件描述符。
3.每次在调用开始时,要把当前进程放入各个文件描述符的等待队列。在调用结束后,又把进程从各个等待队列中删除。
在实际应用中,select/poll监视的文件描述符可能会非常多,如果每次只是返回一小部分,那么,这种情况下select/poll显得不够高效。


  epoll的设计思路,是把select/poll单个的操作拆分为1个epoll_create+多个epoll_ctl+一个epoll_wait。此外,内核针对epoll
操作添加了一个文件系统”eventpollfs”,每一个或者多个要监视的文件描述符都有一个对应的eventpollfs文件系统的inode节点,主要信息保
存在eventpoll结构体中。而被监视的文件的重要信息则保存在epitem结构体中。所以他们是一对多的关系。由于在执行epoll_create和
epoll_ctrl时,已经把用户态的信息保存到内核态了,所以之后即使反复地调用epoll_wait,也不会重复地拷贝参数,扫描文件描述符,反复地
把当前进程放入/放出等待队列。这样就避免了以上的三个缺点。


epoll的优点:
1.支持一个进程打开大数目的socket描述符(FD)
    select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要支持的上万连接数目的
IM服务器来说显然太少了。这时候你一是可以选择修改这个宏然后重新编译内核,不过资料也同时指出这样会带来网络效率的下降,二是可以选择多
进程的解决方案(传统的 Apache方案),不过虽然linux上面创建进程的代价比较小,但仍旧是不可忽视的,加上进程间数据同步远比不上线程间同
步的高效,所以也不是一种完美的方案。不过 epoll则没有这个限制,它所支持的FD上限是最大可以打开文件的数目,这个数字一般远大于2048,举
个例子,在1GB内存的机器上大约是10万左右,具体数目可以cat /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大。
2.IO效率不随FD数目增加而线性下降
    传统的select/poll另一个致命弱点就是当你拥有一个很大的socket集合,不过由于网络延时,任一时间只有部分的socket是"活跃"的,但是
select/poll每次调用都会线性扫描全部的集合,导致效率呈现线性下降。但是epoll不存在这个问题,它只会对"活跃"的socket进行操作---这是
因为在内核实现中epoll是根据每个fd上面的callback函数实现的。那么,只有"活跃"的socket才会主动的去调用 callback函数,其他idle状态
socket则不会,在这点上,epoll实现了一个"伪"AIO,因为这时候推动力在os内核。在一些 benchmark中,如果所有的socket基本上都是活跃的
---比如一个高速LAN环境,epoll并不比select/poll有什么效率,相反,如果过多使用epoll_ctl,效率相比还有稍微的下降。但是一旦使用idle
connections模拟WAN环境,epoll的效率就远在select/poll之上了。
3.使用mmap加速内核与用户空间的消息传递。
    这点实际上涉及到epoll的具体实现了。无论是select,poll还是epoll都需要内核把FD消息通知给用户空间,如何避免不必要的内存拷贝就很重
要,在这点上,epoll是通过内核于用户空间mmap同一块内存实现的。

----------------------------------------------------------------

  poll与select/poll不同的一点是,它是由一组系统调用组成。
#include
typedef union epoll_data {
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
} epoll_data_t;

struct epoll_event {
    __uint32_t events;      /* Epoll events */
    epoll_data_t data;      /* User data variable */
};


int epoll_creae(int size);  
功能:该函数生成一个epoll专用的文件描述符
参数:size为epoll上能关注的最大描述符数

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);  
功能:用于控制某个epoll文件描述符时间,可以注册、修改、删除
参数:epfd由epoll_create生成的epoll专用描述符

    op操作:EPOLL_CTL_ADD 注册   EPOLL_CTL_MOD修改  EPOLL_DEL删除

    fd:关联的文件描述符

    evnet告诉内核要监听什么事件

int epoll_wait(int epfd,struct epoll_event*events,int maxevents,int timeout);  
功能:该函数等待i/o事件的发生。
参数:epfd要检测的句柄

     events:用于回传待处理时间的数组

     maxevents:告诉内核这个events有多大,不能超过之前的size

     timeout:为超时时间

常见的事件如下:
EPOLLIN:表示对描述符的可以读

EPOLLOUT:表示对描述符的可以写

EPOLLPRI:表示对描述符的有紧急数据可以读

EPOLLERR:发生错误

EPOLLHUP:挂起

EPOLLET:边缘触发

EPOLLONESHOT:一次性使用,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里

  epoll_create函数创建epoll文件描述符,参数size并不是限制了epoll所能监听的描述符最大个数,只是对内核初始分配内部数据结构的一个建议。返回是
epoll描述符。-1表示创建失败。
  epoll_ctl 控制对指定描述符fd执行op操作,event是与fd关联的监听事件。op操作有三种:添加EPOLL_CTL_ADD,删除EPOLL_CTL_DEL,修改
EPOLL_CTL_MOD。分别添加、删除和修改对fd的监听事件。
  epoll_wait 等待epfd上的io事件,最多返回maxevents个事件。

------------------------------------------------------------------

epoll的使用方法:
 
通过在包含一个头文件#include 以及几个简单的API将可以大大的提高你的网络服务器的支持人数。
 
首先通过create_epoll(int maxfds)来创建一个epoll的句柄。这个函数会返回一个新的epoll句柄,之后的所有操作将通过这个句柄来进行操作。在用完之后,记得用close()来关闭这个创建出来的epoll句柄。
 
之后在你的网络主循环里面,每一帧的调用epoll_wait(int epfd, epoll_event events, int max events, int timeout)来查询所有的网络接口,看哪一个可以读,哪一个可以写了。基本的语法为:
nfds = epoll_wait(kdpfd, events, maxevents, -1);
 
其中kdpfd为用epoll_create创建之后的句柄,events是一个epoll_event*的指针,当epoll_wait这个函数操作成功之后,epoll_events里面将储存所有的读写事件。max_events是当前需要监听的所有socket句柄数。最后一个timeout是 epoll_wait的超时,为0的时候表示马上返回,为-1的时候表示一直等下去,直到有事件返回,为任意正整数的时候表示等这么长的时间,如果一直没有事件,则返回。一般如果网络主循环是单独的线程的话,可以用-1来等,这样可以保证一些效率,如果是和主逻辑在同一个线程的话,则可以用0来保证主循环的效率。
 
epoll_wait返回之后应该是一个循环,遍历所有的事件。
 
--------------------------------------------------------------------------
epoll程序框架:
for( ; ; )  
   {  
       nfds = epoll_wait(epfd,events,20,500);  
       for(i=0;i        {  
           if(events[i].data.fd==listenfd) //有新的连接  
           {  
               connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept这个连接  
               ev.data.fd=connfd;  
               ev.events=EPOLLIN|EPOLLET;  
               epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //将新的fd添加到epoll的监听队列中  
           }  
 
           else if( events[i].events&EPOLLIN ) //接收到数据,读socket  
           {  
               n = read(sockfd, line, MAXLINE)) < 0    //读  
               ev.data.ptr = md;     //md为自定义类型,添加数据  
               ev.events=EPOLLOUT|EPOLLET;  
               epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改标识符,等待下一个循环时发送数据,异步处理的精髓  
           }  
           else if(events[i].events&EPOLLOUT) //有数据待发送,写socket  
           {  
               struct myepoll_data* md = (myepoll_data*)events[i].data.ptr;    //取数据  
               sockfd = md->fd;  
               send( sockfd, md->ptr, strlen((char*)md->ptr), 0 );        //发送数据  
               ev.data.fd=sockfd;  
               ev.events=EPOLLIN|EPOLLET;  
               epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改标识符,等待下一个循环时接收数据  
           }  
           else  
           {  
               //其他的处理  
           }  
       }  
   }  
---------------------------------------------------------------------------
epoll的程序实例:

 #include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
#include  
 
#define MAXEVENTS 64  
 
//函数:  
//功能:创建和绑定一个TCP socket  
//参数:端口  
//返回值:创建的socket  
static int  
create_and_bind (char *port)  
{  
  struct addrinfo hints;  
  struct addrinfo *result, *rp;  
  int s, sfd;  
 
  memset (&hints, 0, sizeof (struct addrinfo));  
  hints.ai_family = AF_UNSPEC;     /* Return IPv4 and IPv6 choices */  
  hints.ai_socktype = SOCK_STREAM; /* We want a TCP socket */  
  hints.ai_flags = AI_PASSIVE;     /* All interfaces */  
 
  s = getaddrinfo (NULL, port, &hints, &result);  
  if (s != 0)  
    {  
      fprintf (stderr, "getaddrinfo: %s\n", gai_strerror (s));  
      return -1;  
    }  
 
  for (rp = result; rp != NULL; rp = rp->ai_next)  
    {  
      sfd = socket (rp->ai_family, rp->ai_socktype, rp->ai_protocol);  
      if (sfd == -1)  
        continue;  
 
      s = bind (sfd, rp->ai_addr, rp->ai_addrlen);  
      if (s == 0)  
        {  
          /* We managed to bind successfully! */  
          break;  
        }  
 
      close (sfd);  
    }  
 
  if (rp == NULL)  
    {  
      fprintf (stderr, "Could not bind\n");  
      return -1;  
    }  
 
  freeaddrinfo (result);  
 
  return sfd;  
}  
 
 
//函数  
//功能:设置socket为非阻塞的  
static int  
make_socket_non_blocking (int sfd)  
{  
  int flags, s;  
 
  //得到文件状态标志  
  flags = fcntl (sfd, F_GETFL, 0);  
  if (flags == -1)  
    {  
      perror ("fcntl");  
      return -1;  
    }  
 
  //设置文件状态标志  
  flags |= O_NONBLOCK;  
  s = fcntl (sfd, F_SETFL, flags);  
  if (s == -1)  
    {  
      perror ("fcntl");  
      return -1;  
    }  
 
  return 0;  
}  
 
//端口由参数argv[1]指定  
int  
main (int argc, char *argv[])  
{  
  int sfd, s;  
  int efd;  
  struct epoll_event event;  
  struct epoll_event *events;  
 
  if (argc != 2)  
    {  
      fprintf (stderr, "Usage: %s [port]\n", argv[0]);  
      exit (EXIT_FAILURE);  
    }  
 
  sfd = create_and_bind (argv[1]);  
  if (sfd == -1)  
    abort ();  
 
  s = make_socket_non_blocking (sfd);  
  if (s == -1)  
    abort ();  
 
  s = listen (sfd, SOMAXCONN);  
  if (s == -1)  
    {  
      perror ("listen");  
      abort ();  
    }  
 
  //除了参数size被忽略外,此函数和epoll_create完全相同  
  efd = epoll_create1 (0);  
  if (efd == -1)  
    {  
      perror ("epoll_create");  
      abort ();  
    }  
 
  event.data.fd = sfd;  
  event.events = EPOLLIN | EPOLLET;//读入,边缘触发方式  
  s = epoll_ctl (efd, EPOLL_CTL_ADD, sfd, &event);  
  if (s == -1)  
    {  
      perror ("epoll_ctl");  
      abort ();  
    }  
 
  /* Buffer where events are returned */  
  events = calloc (MAXEVENTS, sizeof event);  
 
  /* The event loop */  
  while (1)  
    {  
      int n, i;  
 
      n = epoll_wait (efd, events, MAXEVENTS, -1);  
      for (i = 0; i < n; i++)  
        {  
          if ((events[i].events & EPOLLERR) ||  
              (events[i].events & EPOLLHUP) ||  
              (!(events[i].events & EPOLLIN)))  
            {  
              /* An error has occured on this fd, or the socket is not
                 ready for reading (why were we notified then?) */  
              fprintf (stderr, "epoll error\n");  
              close (events[i].data.fd);  
              continue;  
            }  
 
          else if (sfd == events[i].data.fd)  
            {  
              /* We have a notification on the listening socket, which
                 means one or more incoming connections. */  
              while (1)  
                {  
                  struct sockaddr in_addr;  
                  socklen_t in_len;  
                  int infd;  
                  char hbuf[NI_MAXHOST], sbuf[NI_MAXSERV];  
 
                  in_len = sizeof in_addr;  
                  infd = accept (sfd, &in_addr, &in_len);  
                  if (infd == -1)  
                    {  
                      if ((errno == EAGAIN) ||  
                          (errno == EWOULDBLOCK))  
                        {  
                          /* We have processed all incoming
                             connections. */  
                          break;  
                        }  
                      else  
                        {  
                          perror ("accept");  
                          break;  
                        }  
                    }  
 
                                  //将地址转化为主机名或者服务名  
                  s = getnameinfo (&in_addr, in_len,  
                                   hbuf, sizeof hbuf,  
                                   sbuf, sizeof sbuf,  
                                   NI_NUMERICHOST | NI_NUMERICSERV);//flag参数:以数字名返回  
                                  //主机地址和服务地址  
 
                  if (s == 0)  
                    {  
                      printf("Accepted connection on descriptor %d "  
                             "(host=%s, port=%s)\n", infd, hbuf, sbuf);  
                    }  
 
                  /* Make the incoming socket non-blocking and add it to the
                     list of fds to monitor. */  
                  s = make_socket_non_blocking (infd);  
                  if (s == -1)  
                    abort ();  
 
                  event.data.fd = infd;  
                  event.events = EPOLLIN | EPOLLET;  
                  s = epoll_ctl (efd, EPOLL_CTL_ADD, infd, &event);  
                  if (s == -1)  
                    {  
                      perror ("epoll_ctl");  
                      abort ();  
                    }  
                }  
              continue;  
            }  
          else  
            {  
              /* We have data on the fd waiting to be read. Read and
                 display it. We must read whatever data is available
                 completely, as we are running in edge-triggered mode
                 and won't get a notification again for the same
                 data. */  
              int done = 0;  
 
              while (1)  
                {  
                  ssize_t count;  
                  char buf[512];  
 
                  count = read (events[i].data.fd, buf, sizeof(buf));  
                  if (count == -1)  
                    {  
                      /* If errno == EAGAIN, that means we have read all
                         data. So go back to the main loop. */  
                      if (errno != EAGAIN)  
                        {  
                          perror ("read");  
                          done = 1;  
                        }  
                      break;  
                    }  
                  else if (count == 0)  
                    {  
                      /* End of file. The remote has closed the
                         connection. */  
                      done = 1;  
                      break;  
                    }  
 
                  /* Write the buffer to standard output */  
                  s = write (1, buf, count);  
                  if (s == -1)  
                    {  
                      perror ("write");  
                      abort ();  
                    }  
                }  
 
              if (done)  
                {  
                  printf ("Closed connection on descriptor %d\n",  
                          events[i].data.fd);  
 
                  /* Closing the descriptor will make epoll remove it
                     from the set of descriptors which are monitored. */  
                  close (events[i].data.fd);  
                }  
            }  
        }  
    }  
 
  free (events);  
 
  close (sfd);  
 
  return EXIT_SUCCESS;  

阅读(535) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~