关于EPoll的个人理解

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

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

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

  c.采用了mmap内存映射,减少内核区到用户区数据拷贝,又节省了不少时间。

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

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

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

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

 

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

  单线程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);

          }

 

 

posted on 2018-01-10 12:18  zyz913614263  阅读(215)  评论(0编辑  收藏  举报

导航