关于EPoll的我的理解

1.epoll 是I/o多路复用的一种解决方案,对比select的优势有:面试

  a.支持打开最大的文件描述符(可高达百万)数据库

  b.效率并不随着描述符的增多而线性降低。select每次是轮询,因此描述符越多效率越低。epoll的好处是利用事件触发,内核经过回调函数帮他(这是亲儿子)。服务器

  c.采用了mmap内存映射,减小内核区到用户区数据拷贝,又节省了很多时间。多线程

2.Epoll又分为水平触发和边缘触发。函数

  Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。若是此次没有把数据一次性所有读写完(如读写缓冲区过小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,固然若是你一直不去读写,它会一直通知你!!!若是系统中有大量你不须要读写的就绪文件描述符,而它们每次都会返回,这样会大大下降处理程序检索本身关心的就绪文件描述符的效率!!!spa

  Edge_triggered(边缘触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写。若是此次没有把数据所有读写完(如读写缓冲区过小),那么下次调用epoll_wait()时,它不会通知你,也就是它只会通知你一次,直到该文件描述符上出现第二次可读写事件才会通知你!!!这种模式比水平触发效率高,系统不会充斥大量你不关心的就绪文件描述符!!!线程

  优缺点就很明显了,如过访问量过大,切每一个事件的内容又不少的话,推荐边缘触发,由于每一个事件的读取时要花长时间的,用边缘触发能明显减小事件的触发次数。code

 

3.瓶颈。网上看的,不过却是和咱们的游戏服务器的限制想吻合。blog

  单线程epoll,触发量可达到15000,可是加上业务后,由于大多数业务都与数据库打交道,因此就会存在阻塞的状况,这个时候就必须用多线程来提速。游戏

 

代码实现:从服务端分析,何时该监听读,何时该监听写?(面试被问到过。。。)。从服务器的角度,客户端发送的请求都是须要读取的,因此是读事件,而服务器的返回数据是写事件。正常状况下应该是先读后写,有请求才返回嘛。写完以后这个描述符应该再切换回读事件,监听下次的请求。并且读是个被动事件,写是服务器的主动事件。因此网上大部分代码的实现都是默认监听读,服务器返回数据时将描述符置为写,写完后在还原读。

        if(events[i].data.fd==listenfd)
              {

                  connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
                  if(connfd<0){
                      perror("connfd<0");
                      exit(1);
                  }
                  setnonblocking(connfd);

                  char *str = inet_ntoa(clientaddr.sin_addr);
                    //std::cout<<"connec_ from >>"<
                    //设置用于读操做的文件描述符

                  ev.data.fd=connfd;
                    //设置用于注测的读操做事件

                  ev.events=EPOLLIN|EPOLLET;
                    //注册ev

                  epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
            }
            else if(events[i].events&EPOLLIN)
            {
                    //printf("reading!/n");

                    if ( (sockfd = events[i].data.fd) < 0) continue;
                    new_task=new task();
                    new_task->fd=sockfd;
                    new_task->next=NULL;
                    //添加新的读任务

                    pthread_mutex_lock(&mutex);
                    if(readhead==NULL)
                    {
                      readhead=new_task;
                      readtail=new_task;
                    }
                    else
                    {
                     readtail->next=new_task;
                      readtail=new_task;
                    }
                   //唤醒全部等待cond1条件的线程

                    pthread_cond_broadcast(&cond1);
                    pthread_mutex_unlock(&mutex);
              }
               else if(events[i].events&EPOLLOUT)
               {

            rdata=(struct user_data *)events[i].data.ptr;
            sockfd = rdata->fd;
            write(sockfd, rdata->line, rdata->n_size);
            delete rdata;
            //设置用于读操做的文件描述符
            ev.data.fd=sockfd;
            //设置用于注测的读操做事件
            ev.events=EPOLLIN|EPOLLET;
            //修改sockfd上要处理的事件为EPOLIN
            epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);           }

相关文章
相关标签/搜索