關於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);           }

相關文章
相關標籤/搜索