Linux下EPoll通訊模型簡析

軍說網事發表於2015-06-30

        EPoll基於I/O的事件通知機制,由系統通知使用者那些SOCKET觸發了那些相關I/O事件,事件中包含對應的檔案描述符以及事件型別,這樣應用程式可以針對事件以及事件的source做相應的處理(Acception,Read,Write,Error)。相比原先的SELECT模型(使用者主動依次檢查SOCKET),變成被動等待系統告知處於活躍狀態的SOCKET,效能提升不少(不需要依次遍歷所有的SOCKET,而只是對活躍SOCKET進行事件處理)。

  基本步驟:

  擅長對大量併發使用者的請求進行及時處理,完成伺服器與客戶端的資料互動。一個簡單實現步驟如下:

  (1) 建立偵聽socket:ListenSock,將該描述符設定為非阻塞模式,呼叫Listen()函式在該套接字上偵聽連線請求。

  (2) 使用epoll_create()函式建立檔案描述,設定可管理的最大socket描述符數目。

  (3) 將ListenSock註冊進EPoll中進行監測

  (4) EPoll監視啟動,epoll_wait()等待epoll事件發生。

  (5)如果epoll事件表明有新的連線請求,則呼叫accept()函式,並將新建立連線新增到EPoll中。若為讀寫或者報錯等,呼叫對應的Handle進行處理。

  (6) 繼續監視,直至停止。


上訴過程只是一個簡單的線性例項,在實際的應用過程中,為了提高監視效率,常常將EPOLL監聽到的事件交給其他專門的任務執行緒進行處理,以提高EPoll監視的效率。

  主要涉及API

  1.EPoll建立

  int epoll_create(int size)

  該函式生成一個epoll專用檔案描述符,其中的引數是指定生成描述符的最大範圍。在linux-2.4.32核心中根據size大小初始化雜湊表的大小,在linux2.6.10核心中該引數無用,使用紅黑樹管理所有的檔案描述符,而不是hash.

  2、epoll_ctl函式

  int epoll_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

  epoll_event 結構體的events欄位是表示感興趣的事件,取值為:

  EPOLLIN:表示對應的檔案描述符可以讀;

  EPOLLOUT:表示對應的檔案描述符可以寫;

  EPOLLPRI:表示對應的檔案描述符有緊急的資料可讀;

  EPOLLERR:表示對應的檔案描述符發生錯誤;

  EPOLLHUP:表示對應的檔案描述符被結束通話;

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

  3、事件等待函式

  int epoll_wait(int epfd,struct epoll_event * events,intmaxevents,int timeout)

  該函式用於輪詢I/O事件的發生;

  引數: epfd:由epoll_create 生成的epoll專用的檔案描述符;

  epoll_event:用於回傳等待處理的事件陣列;

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

  timeout:等待I/O事件發生的超時值(ms);-1永不超時,直到有事件產生才觸發,0立即返回

  主要資料結構:

  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 */

  };

  一般我們在程式設計時,利用event變數儲存事件對應的檔案描述符以及事件型別。

  例項程式碼

  伺服器段程式碼

  int EPollServer()

  {

  int srvPort = 6888;

  initSrvSocket(srvPort);

  /* 建立 epoll 控制程式碼,把監聽socket加入到epoll集合裡 */

  epollfd = epoll_create(MAX_EVENTS);

  struct epoll_event event;

  event.events = EPOLLIN | EPOLLET;

  event.data.fd = srvfd;

  if ( epoll_ctl(epollfd, EPOLL_CTL_ADD, srvfd, &event) < 0 )

  {

  printf(“epoll Add Failed: fd=%d\n”, srvfd);

  return -1;

  }

  printf( “epollEngine startup:port %d”, srvPort);

  while(1)

  {

  /*等待事件發生*/

  int nfds = epoll_wait(epollfd, eventList, MAX_EVENTS, -1);

  if ( nfds == -1 )

  {

  printf( “epoll_wait”);

  continue;

  }

  /* 處理所有事件 */

  int n = 0;

  for (; n < nfds; n++)

  handleEvent(eventList + n);

  }

  close(epollfd);

  close(srvfd);

  };

  在事件處理handleEvent中(分為連線事件處理以及資料接收傳送事件)

  void handleEvent(struct epoll_event* pEvent)

  {

  if (pEvent->data.fd == srvfd)

  {

  AcceptConn(srvfd);

  }else{

  RecvData(pEvent->data.fd);

  SendData(pEvent->data.fd);

  epoll_ctl(epollfd, EPOLL_CTL_DEL, pEvent->data.fd, pEvent);

  }

  }

  //從標準輸入讀取資料,傳送給伺服器端,伺服器端在原樣返回,客戶端再接收並予以顯示

  void handle(int sockfd)

  {

  char sendline[MAXLINE];

  char recvline[MAXLINE];

  int n;

  for (;;) {

  if (fgets(sendline, MAXLINE, stdin) == NULL)

  break;

  if (read(STDIN_FILENO, sendline, MAXLINE) == 0)

  break;

  n = write(sockfd, sendline, strlen(sendline));

  n = read(sockfd, recvline, MAXLINE);

  if (n == 0) {

  printf(“echoclient: server terminatedprematurely\n”);

  break;

  }

  write(STDOUT_FILENO, recvline, n);

  //如果用標準庫的快取流輸出有時會出現問題

  //fputs(recvline, stdout);

  }

  }

  執行結果(Linux下截圖麻煩,直接複製控制檯結果)

  客戶端:

  administrator@ubuntu:~$ ./echoclient

  welcome to echoclient

  123456

  123456

  伺服器端:

  administrator@ubuntu:~/source/EPollProject$ ./EPoll

  epollEngine startup port 6888

  handleEvent function, HANDLE: 3, EVENT is 1

  Accept Connection: 5

  handleEvent function, HANDLE: 5, EVENT is 1

  RecvData function

  SOCKET HANDLE: 5: CONTENT: 123456

  content is 123456

  SendData function

  SendData: 123456

  注:

  1.此處只是學習了EPoll基本模型,在實際應用中,為了提高EPoll模型的監視效率,一般在監視執行緒中只做監視,不過事件處理工作,而是將事件交付其他執行緒處理。

  2. 為了提高事件處理的效率,所以我們儘量避免在有事件時開闢執行緒處理,處理完關閉,一般在系統啟動時會建立執行緒池,將事件交與執行緒池中的空閒執行緒進行處理。在事件的處理過程中不會有縣城的建立、銷燬等操作。效率也提高了。




相關文章