欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

epoll

程序员文章站 2024-03-23 18:42:16
...

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率.epoll是当前在Linux下开发大规模并发网络程序的热门人选,epoll 在Linux2.6内核中正式引入,和select相似,都是I/O多路复用(IO multiplexing)技术,按照man手册的说法:是为处理大批量句柄而作了改进的poll。epoll的特点:epoll对于句柄事件的选择不是遍历的,是事件响应的,就是句柄上事件来就马上选择出来,不需要遍历整个句柄链表,因此效率非常高,内核将句柄用红黑树保存的。

同 I/O多路复用和信号驱动I/O一样,Linux的epoll(event poll)API可以检查描述符上的I/O就绪状态。

fd(文件描述符)

select 最不能忍受的是一个进程所打开的FD是有一定限制的,由FD_SETSIZE设置,默认值是2048。对于那些需要

::表示全局,这样可以声明全局函数

 

bool TTcpServer::Listen(const unsigned short port)
{
    if (m_IsListen)
        return false;
    
    //socket
    int accept_fd = ::socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK | SOCK_CLOEXEC, IPPROTO_TCP); 
    if (accept_fd < 0)
        return false;
    
    //端口重用,快速重启服务
    int opt = 1;
    ::setsockopt(accept_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    
    //bind
    sockaddr_in addr;
    memset(&addr, 0, sizeof(sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    ::inet_pton(AF_INET, "0.0.0.0", &addr.sin_addr);
    if (0 != ::bind(accept_fd, (sockaddr*)&addr, (socklen_t)sizeof(sockaddr_in)))
    {
		perror("bind failed");
        return false;
    }
    
    //listen
    if (0 != ::listen(accept_fd, SOMAXCONN))
    {
        perror("listen failed");
        return false;
    }
        
    //add epoll
    epoll_event event;
    event.data.u64 = 0; //清空数据
    event.data.fd = accept_fd;    //绑定client指针
    event.events = POLLIN | POLLPRI; 
    ::epoll_ctl(m_Poll_Fd, EPOLL_CTL_ADD, accept_fd, &event);  
    
    //启动accept工作线程
    Start();
    
    //启动eventloop工作线程
    for(std::vector<TEventLoop*>::iterator iter = m_EventLoops.begin(); iter != m_EventLoops.end(); iter++)
        (*iter)->Start();
    
    //end
    m_IsListen = true;
    return true;    
}

 

2.1 socket函数

  int socket(int protofamily, int type, int protocol);
  socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字,而socket()用于创建一个socket描述符(socket descriptor),它唯一标识一个socket。这个socket描述字跟文件描述字一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
  正如可以给fopen的传入不同参数值,以打开不同的文件。创建socket的时候,也可以指定不同的参数创建不同的socket描述符,socket函数的三个参数分别为:
  protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
  type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等(socket的类型有哪些?)。
  protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议(这个协议我将会单独开篇讨论!)。
  注意:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
  当我们调用socket创建一个socket时,返回的socket描述字它存在于协议族(address family,AF_XXX)空间中,但没有一个具体的地址。如果想要给它赋值一个地址,就必须调用bind()函数,否则就当调用connect()、listen()时系统会自动随机分配一个端口。

 

 

epoll API的核心数据结构称作epoll实例,它和一个打开的文件描述符相关联。这个文件描述符不是用来做I/O操作的,相反,它是内核数据结构的句柄,这些内核数据结构实现了两个目的。

  • 记录了在进程中声明过的感兴趣的文件描述符列表——兴趣列表。
  • 维护了处于I/O就绪态的文件描述符列表——就绪列表。

对于由epoll检查的每一个文件描述符,我们可以指定一个位掩码来表示我们感兴趣的事件。

epoll API由以下3个系统调用组成。

  • 系统调用epoll_creat()创建一个epoll实例,返回代表该实例的文件描述符。
  • 系统调用epoll_ctl()操作同epoll实例相关联的兴趣列表。通过epoll_ctl(),我们可以增加新的描述符到列表中,将已有的文件描述符从该列表中移除,以及修改代表描述符上事件类型的位掩码。
  • 系统调用epoll_wait()返回与epoll实例相关联的就绪列表中的成员。

 

/*创建一个epoll实例。为新实例返回一个fd。

“size”参数是指定文件数量的提示

与新实例相关联的描述符。fd的

epoll_create()返回的值应该用close()关闭。*/

/*与epoll_create相同,但带有标志参数。未使用的尺寸

参数被删除。*/

作为函数返回值,epoll_create()返回了代表新创建的epoll实例的文件描述符。这个文件描述符在其他几个epoll系统调用中用来表示epoll实例。当这个文件描述符不再需要时,应该通过close()来关闭。当所有与epoll实例相关的文件描述符都被关闭时,实例被销毁,相关的资源都返还给系统。(多个文件描述符可能引用到相同的epoll实例,这是由于调用了fork()或者dup()这样类似的函数所致。)

从2.6.27版内核以来,Linux支持了一个新的系统调用epoll_create1()。该系统调用执行的任务同epoll_create()一样,但是去掉了无用的参数size,并增加了一个可用来修改系统调用行为的flags参数。目前只支持一个flag标志:EPOLL_CLOEXEC,它使得内核在新的文件描述符上启动了执行即关闭标志。

 

2.修改epoll的兴趣列表:epoll_ctl()

系统调用epoll_ctl()能够修改由文件描述符epfd所代表的epoll实例中的兴趣列表。