網絡編程—epoll

一、原理

epoll使用mmap減少複製開銷。 並且epoll使用“事件”的就緒通知方式,通過epoll_ctl註冊fd,一旦該fd就緒,內核就會採用類似callback的回調機制來激活該fd,epoll_wait便可以收到通知。

二、最大連接數

雖然連接數有上限,但是很大,1G內存的機器上可以打開10萬左右的連接,2G內存的機器可以打開20萬左右的連接

三、效率

因爲epoll內核中實現是根據每個fd上的callback函數來實現的,只有活躍的socket纔會主動調用callback,所以在活躍socket較少的情況下,使用epoll沒有前面兩者的線性下降的性能問題,但是所有socket都很活躍的情況下,可能會有性能問題。

四、消息傳遞

epoll通過內核和用戶空間共享一塊內存來實現的,內核於用戶空間mmap同一塊內存。

五、EPOLL模型的工作模式
   (1) LT模式

   LT:level triggered,這是缺省的工作方式,同時支持block和no-block socket,在這種模式中,內核告訴你一個文件描述符是否就緒了,然後你可以對這個就緒的fd進行IO操作。如果你不作任何操作,內核還是會繼續通知你的,所以,這種模式編程出錯誤可能性要小一點。傳統的select/poll都是這種模型的代表。

   (2) ET模式

   LT:edge-triggered,這是高速工作方式,只支持no-block socket。在這種模式下,當描述符從未就緒變爲就緒時,內核就通過epoll告訴你,然後它會假設你知道文件描述符已經就緒,並且不會再爲那個文件描述符發送更多的就緒通知,直到你做了某些操作而導致那個文件描述符不再是就緒狀態(比如你在發送,接收或是接受請求,或者發送接收的數據少於一定量時導致了一個EWOULDBLOCK 錯誤)。但是請注意,如果一直不對這個fd作IO操作(從而導致它再次變成未就緒),內核就不會發送更多的通知(only once)。不過在TCP協議中,ET模式的加速效用仍需要更多的benchmark確認。

六、EPOLL模型的使用方法
   epoll用到的所有函數都是在頭文件sys/epoll.h中聲明的,

1.使用的數據結構: epoll_data、epoll_data_t、epoll_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 events */

      epoll_data_t data; /* User data variable */

   };

   epoll_event 結構體的events字段是被觸發的事件,可能的取值爲:

   EPOLLIN:      表示對應的文件描述符可以讀;

   EPOLLOUT:     表示對應的文件描述符可以寫;

   EPOLLPRI:     表示對應的文件描述符有緊急的數據可讀;

   EPOLLERR:     表示對應的文件描述符發生錯誤;

   EPOLLHUP:     表示對應的文件描述符被掛斷;

   EPOLLET:      表示對應的文件描述符有事件發生;

   聯合體epoll_data用來保存觸發事件的某個文件描述符相關的數據。例如一個client連接到服務器,服務器通過調用accept函數可以得到於這個client對應的socket文件描述符,可以把這文件描述符賦給epoll_data的fd字段,以便後面的讀寫操作在這個文件描述符上進行。

2.使用的函數:

   (1)epoll_create

   函數聲明:intepoll_create(int size)

   函數說明:該函數生成一個epoll專用的文件描述符,其中的參數是指定生成描述符的最大範圍,告訴內核要監聽的數目。

   (2) epoll_ctl函數

   函數聲明:intepoll_ctl(int epfd,int op, int fd, struct epoll_event *event)

   函數說明:該函數用於控制某個文件描述符上的事件,可以註冊事件、修改事件、刪除事件。

      epfd:由 epoll_create 生成的epoll專用的文件描述符;

      op:要進行的操作,可能的取值EPOLL_CTL_ADD 註冊、EPOLL_CTL_MOD 修改、EPOLL_CTL_DEL      刪除;

      fd:關聯的文件描述符;

      event:指向epoll_event的指針;

   如果調用成功則返回0,不成功則返回-1。

   (3) epoll_wait函數

   函數聲明:int epoll_wait(int epfd, structepoll_event * events, int maxevents, int timeout)

   函數說明:該函數用於輪詢I/O事件的發生。

   epfd:由epoll_create 生成的epoll專用的文件描述符;

   epoll_event:用於回傳代處理事件的數組;

   maxevents:每次能處理的事件數;

   timeout:等待I/O事件發生的超時值;

   返回發生事件數。

   調用流程:

   1.通過create_epoll(int maxfds)來創建一個epoll的句柄,其中maxfds爲你的epoll所支持的最大句柄數。這個函數會返回一個新的epoll句柄,之後的所有操作都將通過這個句柄來進行操作。在用完之後,記得用close()來關閉這個創建出來的epoll句柄。

   2.在網絡主循環裏面,調用epoll_wait(int epfd, epoll_event events, int max_events,int timeout)來查詢所有的網絡接口,看哪一個可以讀,哪一個可以寫。基本的語法爲:

   nfds = epoll_wait(kdpfd, events, maxevents, -1); 

   其中kdpfd爲用epoll_create創建之後的句柄,events是一個epoll_event*的指針,當epoll_wait函數操作成功之後,events裏面將儲存所有的讀寫事件。max_events是當前需要監聽的所有socket句柄數。最後一個timeout參數指示 epoll_wait的超時條件,爲0時表示馬上返回;爲-1時表示函數會一直等下去直到有事件返回;爲任意正整數時表示等這麼長的時間,如果一直沒有事件,則會返回。一般如果網絡主循環是單線程,用-1來等待可以保證效率,如果是和主循環在同一個線程,可以用0來保證主循環的效率。

3.epoll_wait返回之後,需要進入一個循環,遍歷所有的事件。


七、EPOLL模型的一個實例
#include <iostream>

#include <sys/socket.h> 

#include <sys/epoll.h>

#include <netinet/in.h> 

#include <arpa/inet.h>

#include <fcntl.h> 

#include <unistd.h> 

#include <stdio.h>

 

#define MAXLINE 10 

#define OPEN_MAX 100

#define LISTENQ 20

#define SERV_PORT 5555 

#define INFTIM 1000

 

void setnonblocking(int sock)

{

   int opts;

   opts = fcntl(sock, F_GETFL);

   if(opts < 0)

   {

      perror("fcntl(sock, GETFL)");

      exit(1);

   }

   opts = opts | O_NONBLOCK;

   if(fcntl(sock, F_SETFL, opts) < 0)

   {

      perror("fcntl(sock,SETFL,opts)");

      exit(1);

   }

}

int main()

{

   int i, maxi, listenfd, connfd, sockfd, epfd, nfds; 

   ssize_t n; 

   char line[MAXLINE];

   socklen_t clilen;

   //聲明epoll_event結構體的變量, ev用於註冊事件, events數組用於回傳要處理的事件

   struct epoll_event ev,events[20];

   //生成用於處理accept的epoll專用的文件描述符, 指定生成描述符的最大範圍爲256 

   epfd = epoll_create(256);

   struct sockaddr_in clientaddr; 

   struct sockaddr_in serveraddr;

   listenfd = socket(AF_INET, SOCK_STREAM, 0);

 

   setnonblocking(listenfd);       //把用於監聽的socket設置爲非阻塞方式

   ev.data.fd = listenfd;          //設置與要處理的事件相關的文件描述符

   ev.events = EPOLLIN | EPOLLET;  //設置要處理的事件類型

   epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);     //註冊epoll事件

   bzero(&serveraddr, sizeof(serveraddr)); 

   serveraddr.sin_family = AF_INET;

   char *local_addr = "200.200.200.204";

   inet_aton(local_addr, &(serveraddr.sin_addr));

   serveraddr.sin_port = htons(SERV_PORT);  //或者htons(SERV_PORT);

   bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));

   listen(listenfd, LISTENQ);

   maxi = 0;

   for( ; ; )

   { 

      nfds = epoll_wait(epfd, events, 20, 500); //等待epoll事件的發生

      for(i = 0; i < nfds; ++i)                 //處理所發生的所有事件

      {

         if(events[i].data.fd == listenfd)      //監聽事件

         {

            connfd = accept(listenfd, (sockaddr *)&clientaddr, &clilen); 

            if(connfd < 0)

            {

               perror("connfd<0");

               exit(1);

            }

            setnonblocking(connfd);           //把客戶端的socket設置爲非阻塞方式

            char *str = inet_ntoa(clientaddr.sin_addr);

            std::cout << "connect from " << str  <<std::endl;

            ev.data.fd=connfd;                //設置用於讀操作的文件描述符

            ev.events=EPOLLIN | EPOLLET;      //設置用於注測的讀操作事件

            epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);

            //註冊ev事件

         }

         else if(events[i].events&EPOLLIN)      //讀事件

         {

            if ( (sockfd = events[i].data.fd) < 0)

            {

               continue;

            }

            if ( (n = read(sockfd, line, MAXLINE)) < 0) // 這裏和IOCP不同

            {

               if (errno == ECONNRESET)

               {

                  close(sockfd);

                  events[i].data.fd = -1; 

               }

               else

               {

                  std::cout<<"readline error"<<std::endl;

               }

            }

            else if (n == 0)

            {

               close(sockfd); 

               events[i].data.fd = -1; 

            }

            ev.data.fd=sockfd;              //設置用於寫操作的文件描述符

            ev.events=EPOLLOUT | EPOLLET;   //設置用於注測的寫操作事件 

            //修改sockfd上要處理的事件爲EPOLLOUT

            epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);

         } 

         else if(events[i].events&EPOLLOUT)//寫事件

         {

            sockfd = events[i].data.fd;

            write(sockfd, line, n);

            ev.data.fd = sockfd;               //設置用於讀操作的文件描述符

            ev.events = EPOLLIN | EPOLLET;     //設置用於註冊的讀操作事件

            //修改sockfd上要處理的事件爲EPOLIN

            epoll_ctl(epfd, EPOLL_CTL_MOD, sockfd, &ev);

         } 

      }

   }

}

 


 

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