Unix-epoll

Posted illfuckingkyzb

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Unix-epoll相关的知识,希望对你有一定的参考价值。

 本人不才,只能从表面理解epoll函数的机制,看了很多博客,由于缺乏基础知识,所以对内核中的实现和其数据结构理解不到位,粗浅地来认识一下。

系统打开的最大文件描述符 也是有限制的,并且这个最大量和内存有关

cat /proc/sys/fs/file-max
194720
 
int  epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);\添加套接字
typedef union epoll_data{
void    *ptr;
int        fd;
_uint32_t u32;
_uint64_t u64;
}epoll_data_t;
struct epoll_event{
 _uint32_t events;
 epoll_data_t data;\epoll高效的原因
};

.........................                                                                                                                                                                                                                                                                                                                                                        

看一下select低效的原因
int nready=select(maxfd+1,&rset,NULL,NULL,NULL);//select的实现其实是对maxfd+1个描述符全部进行遍历,没发生事件的位被清除并且同时将发生事件描述符拷贝至rset。全部遍历maxfd+1检测到有IO事件后,将发生事件的个数返回给nready,
从这里可以看出,
select经历了一次遍历和拷贝过程。
时间复杂度为O(n)

同理poll也一样是O(n).

接着看epoll:

epoll_create(int size)//这个函数是创建一个哈希表,size表示哈希表的容量
epoll_create1(int flags)//这个是实现了一棵红黑树,不需要指定容量

 

技术分享图片
#include<stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include<iostream>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include<sys/select.h>
#include<errno.h>
#include<sys/poll.h>
#include<vector>
#include<algorithm>
#include<sys/signal.h>
#include<fcntl.h>
#include<sys/epoll.h>
using namespace std;
typedef std::vector<struct epoll_event> EventList;
#define ERR_EXIT(m)     do    {        perror(m);        exit(EXIT_FAILURE);    }while(0)
void activate_nonblock(int fd){
    int ret;
    int flags=fcntl(fd,F_GETFL);
    if(flags==-1)
        ERR_EXIT("FCNTL");
    flags|=O_NONBLOCK;
    ret=fcntl(fd,F_SETFL,flags);
    if(ret==-1)
        ERR_EXIT("fcntl");
}
int main()
{   signal(SIGPIPE,SIG_IGN);
    //signal(SIGPIPE,handler);
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1)
    {
        perror("socket() err");
        return -1;
    }
    int on = 1;
    if (setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) == -1)
    {
        perror("setsockopt() err");
        return -1;
    }
    int conn;
    vector<int> client;
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8888);
    addr.sin_addr.s_addr = inet_addr("127.0.0.1");
    if (bind(sockfd, (struct sockaddr *) &addr, sizeof(addr)) == -1)
    {
        perror("bind() err");
        return -1;
    }
    if (listen(sockfd, SOMAXCONN) == -1)
    {
        perror("bind() err");
        return -1;
    }
    struct sockaddr_in peeraddr;
   int nready;
   int epollfd;
   struct epoll_event event;
   event.data.fd=sockfd;
   event.events=EPOLLIN|EPOLLET;
   epollfd=epoll_create1(EPOLL_CLOEXEC);
   epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&event);//将sockfd和event加入epollfd进行管理
   EventList Events(16);
   int count=0;
   while(1){
       int i;socklen_t peerlen;
       nready=epoll_wait(epollfd,&*Events.begin(),static_cast<int>(Events.size()),-1);
       if((size_t)nready==Events.size())
           Events.resize(Events.size()*2);
       for( i=0;i<nready;i++){
           if(Events[i].data.fd==sockfd){
               peerlen=sizeof(peeraddr);
               conn=accept(sockfd,(struct sockaddr *)&peeraddr,&peerlen);
               printf("ip=%s port=%d
",inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
               printf("count=%d
",++count);
               client.push_back(conn);
               activate_nonblock(conn);
               event.data.fd=conn;
            event.events=EPOLLIN|EPOLLET;
               epoll_ctl(epollfd,EPOLL_CTL_ADD,conn,&event);
           }
           else if(Events[i].events&EPOLLIN){
               conn=Events[i].data.fd;
               if(conn<0)
                   continue;
            char recvbuf[1024]={0};
            int ret=read(conn,recvbuf,1024);
            if(ret==-1)
                ERR_EXIT("read");
            if(ret==0){
                printf("client close
");
                close(conn);
                event=Events[i];
                epoll_ctl(epollfd,EPOLL_CTL_DEL,conn,&event);
                client.erase(std::remove(client.begin(),client.end(),conn),client.end());

            }
            fputs(recvbuf,stdout);
            write(conn,recvbuf,strlen(recvbuf));
           }
       }

   }
}
epoll_serverepoll

 

 

epoll可以理解为event poll,不同于忙轮询和无差别轮询,epoll会把哪个流发生了怎样的I/O事件通知我们。所以我们说epoll实际上是事件驱动(每个事件关联上fd)的,此时我们对这些流的操作都是有意义的。(复杂度降低到了O(1))

通过epoll_ctl函数添加进来的事件都会被放在红黑树的某个节点内,所以,重复添加是没有用的。当把事件添加进来的时候时候会完成关键的一步,那就是该事件都会与相应的设备(网卡)驱动程序建立回调关系,当相应的事件发生后,就会调用这个回调函数,该回调函数在内核中被称为:ep_poll_callback,这个回调函数其实就所把这个事件添加到rdllist这个双向链表中。一旦有事件发生,epoll就会将该事件添加到双向链表中。那么当我们调用epoll_wait时,epoll_wait只需要检查rdlist双向链表中是否有存在注册的事件,效率非常可观。这里也需要将发生了的事件复制到用户态内存中即可。

 

 

 

也就是说我们得到epoll_wait返回准备好的时间并不需要遍历完整个描述符。这和底层实现有关,由于能力不足,只能理解到这。以后再慢慢补充







以上是关于Unix-epoll的主要内容,如果未能解决你的问题,请参考以下文章

VSCode自定义代码片段——CSS选择器

谷歌浏览器调试jsp 引入代码片段,如何调试代码片段中的js

片段和活动之间的核心区别是啥?哪些代码可以写成片段?

VSCode自定义代码片段——.vue文件的模板

VSCode自定义代码片段6——CSS选择器

VSCode自定义代码片段——声明函数