对select和epoll的理解

我们目前的网络模型大都是epoll的,因为epoll模型会比select模型性能高很多, 尤其在大连接数的情况下,作为后台开发人员需要理解其中的原因。

下面根据自己对selectepoll的理解写下这边文章,希望对大家有帮助,欢迎大家挑战:)

select的特点:select 选择句柄的时候,是遍历所有句柄,也就是说句柄有事件响应时,select需要遍历所有句柄才能获取到哪些句柄有事件通知,因此效率是非常底的。但是如果连接很少的情况下, selectepollLT触发模式相比, 性能上差别不大。

这里要多说一句,select支持的句柄数是有限制的, 同时只支持1024个,这个是句柄集合限制的,如果超过这个限制,很可能导致溢出,而且非常不容易发现问题, TAF就出现过这个问题, 调试了n天,才发现:)

epoll的特点:epoll对于句柄事件的选择不是遍历的,是事件响应的,就是句柄上事件来就马上选择出来,不需要遍历整个句柄链表,因此效率非常高(不知道内核怎么实现的)。但是对于epoll而言还有ETLT的区别,LT表示水平触发,ET表示边缘触发,两者在性能以及代码实现上差别也是非常大的。

LT是缺省的工作方式,并且同时支持block和no-block socket;在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表。
ET是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述 符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就 绪),内核不会发送更多的通知。

LT:水平触发,效率会低于ET触发,尤其在大并发,大流量的情况下。但是LT对代码编写要求比较低,不容易出现问题。LT模式服务编写上的表现是:只有数据没有被获取,内核就不断通知你,因此不用担心事件丢失的情况。

ET:边缘触发,效率非常高,在并发,大流量的情况下,会比LT少很多epoll的系统调用,因此效率高。但是对编程要求高,需要细致的处理每个请求,否则容易发生丢失事件的情况。

下面举一个列子来说明LTET的区别(都是非阻塞模式,阻塞就不说了,效率太低):

采用LT模式下, 如果accept调用有返回就可以马上建立当前这个连接了,再epoll_wait等待下次通知,和select一样。

但是对于ET而言,如果accpet调用有返回,除了建立当前这个连接外,不能马上就epoll_wait还需要继续循环accpet,直到返回-1,且errno==EAGAINTAF里面的示例代码:

      if(ev.events & EPOLLIN)
      {
          do
          {
               struct sockaddr_in stSockAddr;

               socklen_t iSockAddrSize = sizeof(sockaddr_in);

                TC_Socket cs;

                cs.setOwner(false);

                //接收连接
                TC_Socket s;

                s.init(fd, false, AF_INET);

                int iRetCode = s.accept(cs, (struct sockaddr *) &stSockAddr, iSockAddrSize);

                if (iRetCode > 0)
                {

                        ...建立连接

                }

                else

                {    

                    //直到发生EAGAIN才不继续accept
                    if(errno == EAGAIN)
                    {
                            break;
                    }

                }

             }while(true);
       }

同样,recv/send等函数, 都需要到errno==EAGAIN

 

上面介绍selectepoll(LT,ET)区别,从性能的数据上考虑,根据自己的对服务性能的了解,大概数据如下:(2CPU2.8G)

select模型:少连接情况下面,单网络线程,吞吐量大约在1w/s左右,连接数大了,效率下降比较快。(ICE采用这种模型,ICE的吞吐量大约在8k/s,以及以前负责图铃时写的select prefork模型的吞吐量大概也是这个量级)

epollLT):单网络线程,吞吐量大约在1.2w/s左右,连接数增大的情况下, 吞吐量不会有太大变化。(GNP目前采用这种方式,不过新版本的GNP 马上就只要支持ET了)

epoll(ET:单网络线程,吞吐量可以到3w以上,很明显比LT会高很多,如果网络线程增加,吞吐量还会增加,我估计到5w应该不成问题(目前TAF采用这种方式)

ET模式,实际是减少了陷入内核的次数,减少了系统调用。

epoll的内部实现是使用红黑树代替了select用的位数组方式,能快速的定位。

红黑树应该算是Linux内核中用的最复杂的数据结构了,在内存管理等部分也有使用,除了reiserfs中使用的B+树。

select可支持的描述符的数量通过修改内核代码重编内核,可以支持超过1024个,但是位数组遍历的效率实在太低了,呵呵。

// 从网上看到一个代码, 解释ETLT的区别, 比较生动

//   拷贝可以直接编译         g++ -g -Wall aaa.cpp

 

#include <stdio.h>
#include <iostream>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
 
using namespace std;

#define  MAXLINE 4
 
void
setnonblocking (int sock)
{
    int opts;
    opts = fcntl (sock, F_GETFL);
    if (opts < 0)
    {   
        cout << "fcntl(sock,GETFL)" << endl;
        exit (1);
    }   
    opts = opts | O_NONBLOCK;
    if (fcntl (sock, F_SETFL, opts) < 0)
    {        
        cout << "fcntl(sock,SETFL,opts)" << endl;
        exit (1);
    }   
};
 
int main(int argc, char *argv[])
{
    int epfd = epoll_create(256);
    int fds[2];
    pipe(fds);
    setnonblocking (fds[0]);
    int i, n;
    char line[MAXLINE];
    struct epoll_event ev, events[20];
    int nfds;
    int ret;
    int fk = fork();
    
    if (!fk)
    {
        //printf("writing something : %d\n", fds[1]);
        for(;;)
        {
            ret = write(fds[1], "123456", 6);            
            printf("\nwrite size n = %d, content= [123456] ", ret);    cout << endl;
            sleep(2);
        }
 
    }
    
    
    // 改动这里,看到的结果是不一样的
    ev.events = EPOLLIN | EPOLLET;
    //ev.events = EPOLLIN;

    
    ev.data.fd = fds[0];
    ret = epoll_ctl (epfd, EPOLL_CTL_ADD, fds[0], &ev);
    //printf("ctl ret = %dn", ret);  cout << endl;
    
    for(;;)
    {
        nfds = epoll_wait (epfd, events, 20, -1);
        for (i = 0; i < nfds; ++i)
        {
            //printf("reading something from fds=: %d", fds[0]); cout << endl;
            if (events[i].events & EPOLLIN) 
            {
                //printf("reading something : %dn", fds[0]);
                if ((n = read (fds[0], line, sizeof(line))) < 0)
                {
                    printf("are you kidding? %d", (errno)); cout << endl;
                    exit(1);
                }
                else if (n == 0)
                {
                    printf("read 0 bytes."); cout << endl;
                    exit(1);
                }
                
                printf("read size  n = %d, content= [", n); 
                int j;
                for(j = 0; j < n; j++)
                {
                    printf("%c", line[j]);
                }
                
                cout <<"]"<< endl;
                //line[n] = ' ';
                //printf("read : %sn", line);
                //memset(line, 0, sizeof(line));
            }
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值