epoll以及ET、LT模式

相信大家都知道,poll和select可以同時進行多個文件描述符的監聽與處理,那麼爲什麼還要採用epoll方式呢?

一:爲什麼使用epoll

     隨着客戶端的增加,這兩個函數需要將用戶空間的數據拷貝到內核空間,在內核中遍歷輪詢,這就相當耗時,容易出現瓶頸,而epoll函數就不需要用戶態與內核態的切換,並且沒有采用輪詢方式,但如果文件描述符數目衆多,但就緒數目少時,epoll函數就顯示出了其獨特優勢,它是在每個文件描述上註冊一個回調函數,當這個文件描述符上發生了我們所關心的事件時,就將其添加到就緒隊列中進行處理,非輪詢方式。

二:epoll系統調用

    1. 三個函數

         int epoll_create(int size): 創建一個內核事件表,底層爲紅黑樹

         int epoll_ctl(int epfd,int op,int fd,struct epoll_event* event):將我們關心的文件描述符添加到內核事件表中,op可取添加,刪除以及修改操作

         int epoll_wait(int epfd,struct epoll_event *event,int maxevents,int timeout):將我們關心的文件描述符上已經準備就緒的文件描述符返回給我們,返回就緒的文件描述符個數,該函數如果檢測到事件,就將所有就緒事件從內核事件表中複製到他的第二個參數指向的數組中,這個數組只用於輸出epoll_wait檢測到的就緒事件,而不像select與poll的數組參數那樣既用於傳入用戶註冊的事件,又用於輸出內核檢測到的就緒事件。

    2. 在關閉連接前,需要先將文件描述符從內核事件表中刪除,順序不能顛倒,一旦顛倒,已經關閉的文件描述符要從內核事件中刪除,就會造成Bad file descriptor錯誤

    3. ET與LT模式

        ET模式爲高效模式,它不同於LT模式的地方就在於,在epoll_wait檢測到數據就緒並將此事件通知給應用程序之後,應用程序必須立即處理該事件,因爲後續該函數調用便不再向應用程序通知這一件事,所以ET模式在很大程度上降低了同一個epoll事件被重複觸發的次數,效率要高一些。

       LT模式爲默認工作模式,當epoll_wait檢測到數據就緒並將此事件通知給應用程序之後,應用程序不必立即處理該事件,因爲後續該函數調用還會一直嚮應用程序通知這一件事,直到該事件被處理。

       所以當客戶端輸入一個字符串,而服務器端每次只接收一個字符時,在默認得LT模式下,便是輸出字符串長度加1次之後纔會進行下一步,而在ET模式下,可以只接收一個字符,然後便不再提醒,直接進行下一步,可能在下一步之後纔會再輸出第一次的剩餘數據。但ET模式易造成數據丟失,所以我們可以在服務器端循環以一個字符接收並打印的過程,但當所有數據接收完成之後,recv函數便會阻塞,從而導致無法進行下一步,所以再循環同時,還應設置使recv爲非阻塞模式。

   4. 使用epoll系統調用的服務器端代碼

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <unistd.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include<sys/select.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/epoll.h>
#define MAX 30


int create_socket();
void epoll_add(int epfd,int fd);//add a events to inside events table
void epoll_del(int epfd,int fd);//delete a sockfd from inside events table
void epoll_add(int epfd,int fd)
{
       struct epoll_event ev;
      ev.events=EPOLLIN;
      ev.data.fd=fd;
      if(epoll_ctl(epfd,EPOLL_CTL_ADD,fd,&ev)==-1)
      {
           perror("epoll_add error\n");
      }
 }
void epoll_del(int epfd,int fd)
{
     if(epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL)==-1)
     {
         perror("epoll_del error\n");
      }
}
int main()
{
    int sockfd=create_socket();
    assert(sockfd!=-1);

    int epfd=epoll_create(MAX);
    assert(epfd!=-1);
    struct epoll_event events[MAX];
    epoll_add(epfd,sockfd);
    while(1)
    {
         int n=epoll_wait(epfd,events,MAX,5000);
         if(n==-1)
         {
              perror("epoll_wait error\n");
              continue;
         }
         else if(n==0)
         {
                printf("time out\n");
                continue;
         }
        else
        {
             int i=0;
             for(;i<n;++i)
              {

                       if(events[i].events & EPOLLIN)
                        {
                             if(events[i].data.fd==sockfd)
                            {
                                   struct sockaddr_in caddr;
                                   int len=sizeof(caddr);
   
                                   int c=accept(sockfd,(struct sockaddr*)&caddr,&len);
                                    if(c<0)
                                   {
                                         continue;
                                   }
                                   printf("accept ::c=%d\n",c);
                                   epoll_add(epfd,c);
                             }
                            else
                            {
                                     char buff[128]={0};
                                     if(recv(events[i].data.fd,buff,127,0)<=0)//why we can not read many times?because once no data,the recv will block,can not over
                                     {
                                            printf("one client over\n");
                                              //close(events[i].data.fd);
                                            epoll_del(epfd,events[i].data.fd);
                                            close(events[i].data.fd);
                                               continue;
                                      }
                                    printf("read(%d)=%s\n",events[i].data.fd,buff);
                                    send(events[i].data.fd,"ok",2,0);
                             }
                     }
               }
          }
       }
}
int create_socket()
{

         int sockfd=socket(AF_INET,SOCK_STREAM,0);
         assert(sockfd!=-1);

         struct sockaddr_in saddr;
         memset(&saddr,0,sizeof(saddr));
         saddr.sin_family=AF_INET;
         saddr.sin_port=htons(6000);
         saddr.sin_addr.s_addr=inet_addr("192.168.31.44");

          int res = bind(sockfd,(struct sockaddr *)&saddr,sizeof(saddr));
          assert(res!=-1);

           listen(sockfd,5);
           return sockfd;
}   

             

    

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章