《TCP/IP網絡編程》第17章 優於select的epoll

本章所有示例代碼>>gtihub

17.1 epoll理解及應用

1.       基於select的I/O複用技術速度慢的原因

    兩點不合理:

  • 調用select函數後常見的針對所有文件描述符的循環語句;
  • 每次調用select函數時都需要向該函數傳遞監視對象信息;

    “每次調用select函數時向操作系統傳遞監視對象信息。”

    應用程序向操作系統傳遞數據將對程序造成很大負擔,而且無法通過優化代碼解決,因此將成爲性能上的致命弱點。(有些函數不需要操作系統的幫助就能完成功能,而有些則必須藉助於操作系統)

    select函數與文件描述符有關,更準確地說,是監視套接字變化的函數。而套接字是由操作系統管理的,所以select函數絕對需要藉助於操作系統才能完成功能。

    “僅向操作系統傳遞1次監視對象,監視範圍或內容發生變化時只通知發生變化的事項。”(前提是操作系統支持這種處理方式,Linux的支持方式是epoll,Windows的支持方式是IOCP)

2.       select也有優點

    大多數操作系統都支持select函數,只要滿足如下兩個條件,可使用select函數:

  • 服務器接入者少;
  • 程序應具有兼容性;

3.       實現epoll時必要的函數和結構體

    epoll函數優點:

  • 無需編寫以監視狀態變化爲目的的針對所有文件描述符的循環語句;
  • 調用對應於select函數的epoll_wait函數時無需每次傳遞監視對象信息;

    epoll服務器端實現中需要的3個函數:

  • epoll_create:    創建保存epoll文件描述符的空間;
  • epoll_ctl:          向空間註冊並註銷文件描述符;
  • epoll_wait:       與select函數類似,等待文件描述符發生變化;

    select方式中爲了保存監視對象的文件描述符,直接聲明瞭fd_set變量。但epoll方式下由操作系統負責保存監視對象文件描述符,因此需要向操作系統請求創建保存文件描述符的空間,此時使用的函數就是epoll_create

    select方式中,爲了添加和刪除監視對象文件描述符,需要FD_SETFD_CLR函數。但在epoll方式中,通過epoll_ctl函數請求操作系統完成。

    select方式下調用select函數等待文件描述符的變化,而epoll中調用epoll_wait函數。

    select方式中通過fd_set變量查看監視對象的狀態變化(事件發生與否),而epoll方式中通過結構體epoll_event將發生變化的(發生事件的)文件描述符單獨集中到一起。

struct epoll_event
{
    __uint32_t events;
    epoll_data_t data;
};
typedef union epoll_data
{
    void *ptr;
    int fd;
    __uint32_t u32;
    __uint64_t u64;
}epoll_data_t;

    聲明足夠大的epoll_event結構體數組後,傳遞給epoll_wait函數時,發生變化的文件描述符信息將被填入該數組。

4.       epoll_create

#include<sys/epoll.h>
int epoll_create(int size); //成功時返回epoll文件描述符,失敗-1  

    -size:   epoll實例的大小,僅供操作系統參考,Linux2.6.8內核版本後將完全忽略該參數;

    epoll_create函數創建的資源與套接字相同,也由操作系統管理。因此,該函數和創建套接字的情況相同,也會返回文件描述符,主要用於區分epoll例程,終止時也需調用close函數。

5.       epoll_ctl

    生成epoll例程後,應在其內部註冊監視對象文件描述符,此時使用epoll_ctl函數。

 #include<sys/epoll.h>
 int epoll_ctl(intepfd, int op, int fd, struct epoll_event *event);
 //成功時返回0,失敗時返回-1

    -epfd:   用於註冊監視對象的epoll例程;

    -op:      用於指定監視對象的添加、刪除和更改等操作;

    -fd:       需要註冊的監視對象文件描述符;

    -event: 監視對象的事件類

調用:

    epoll_ctl(A, EPOLL_CTL_ADD, B, C);

    “epoll例程A中註冊文件描述符B,主要目的是監視參數C中的事件。”

    epoll_ctl(A, EPOLL_CTL_DEL, B, NULL);

    “從epoll例程A中刪除文件描述符B。”

  • EPOLL_CTL_ADD       :   將文件描述符註冊到epoll例程;
  • EPOLL_CTL_DEL:    從epoll例程中刪除文件描述符;
  • EPOLL_CTL_MOD:  更改註冊的文件描述符的關注事件發生情況;

    epoll_event結構體用於保存發生事件的文件描述符集合。但也可以在epoll例程中註冊文件描述符時,用於註冊關注的事件。

struct epoll_event event;
……
event.events = EPOLLIN; // 發生需要讀取數據的情況(事件)
event.data.fd = sockfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, sockfd, &event);

  • EPOLLIN:                  需要讀取數據的情況;
  • EPOLLOUT:               輸出緩衝爲空,可以立即發送數據的情況;
  • EPOLLPRI:                 收到OOB數據的情況;
  • EPOLLRDHUP:          斷開連接或半關閉的情況,這在邊緣觸發方式下非常有用;
  • EPOLLERR:                發生錯誤的情況;
  • EPOLLLET:                 以邊緣觸發的方式得到事件通知;
  • EPOLLONESHOT:      發生一次事件後,相應文件描述符不再收到事件通知;

    可以通過位或運算同時傳遞多個上述參數。

6.       epoll_wait

#include <sys/epoll.h>
int epoll_wait(intepfd, struct epoll_event *events, int maxevents, int timeout); //成功時返回發生事件的文件描述符數,失敗時返回-1

    -epfd:             表示事件發生監視範圍的epoll例程的文件描述符;

    -events:          保存發生事件的文件描述符集合的結構體地址值;

    -maxevents:    第二個參數中可以保存的最大事件數;

    -timeout:        以1/1000秒爲單位的等待時間,傳遞-1時,一直等待直到發生事件

int event_cnt;
struct epoll_event*ep_event;
……
ep_events =malloc(sizeof(struct epoll_event)*EPOLL_SIZE);
……
event_cnt =epoll_wait(epfd, ep_events, EPOLL_SIZE, -1);
……

    調用函數後,返回發生事件的文件描述符數,同時在第二個參數指向的緩衝中保存發生事件的文件描述符集合。

17.2 條件觸發(Level Trigger)和邊緣觸發(EdgeTrigger)

1.       條件觸發和邊緣觸發的區別在於發生事件的時間點

    如,

    服務器端輸入緩衝收到50字節的數據時,服務器端操作系統將通知該事件(註冊到發生變化的文件描述符)。服務器端讀取20字節後還剩30字節的情況下,仍然會註冊事件。

    邊緣觸發中輸入緩衝收到數據時僅註冊1次該事件。即使輸入緩衝中還留有數據,也不會再進行註冊。

2.       掌握條件觸發的事件特性

    epoll默認以條件觸發方式工作,因此可以通過22-EchoEPLTServer示例驗證

3.       邊緣觸發的服務器端實現中必知的兩點

  • 通過errno變量驗證錯誤原因;
  • 爲了完成非阻塞(Non-blocking)I/O,更改套接字特性;

    爲了在發生錯誤時提供額外的信息,Linux提供瞭如下全局變量:

#include <errno.h>
int errno;

    “read函數發現輸出緩衝中沒有數據可讀時返回-1,同時在error中保存EAGAIN常量。”

    將套接字改爲非阻塞方式的方法:

    Linux提供更改或讀取文件屬性的如下方法。

#include <fcntl.h>
int fcntl(intfiledes, int cmd, ……); // 成功返回cmd參數相關值,失敗時返回-1

    -filedes:     屬性/更改目標的文件描述符;

    -cmd:         表示函數調用的目的;

    fcntl具有可變參數的形式。如果向第二個參數傳遞F_GETFL,可以獲得第一個參數所指的文件描述符屬性;如果向第二個參數傳遞F_SETFL,可以更改文件描述符屬性。

    若希望將文件(套接字)改爲非阻塞模式,需要如下2條語句:

int flag = fcntl(fd,F_GETFL, 0);
fcntl(fd, F_SETFL, flag|O_NONBLOCK);

    通過第一條語句獲取之前設置的屬性信息,通過第二條語句在此屬性基礎上添加非阻塞O_NONBLOCK標誌。

4.       實現邊緣觸發的回聲服務器端

    邊緣觸發方式下,以非阻塞方式工作的read& write函數有可能引起服務器端的長時間停頓。因此,邊緣觸發方式中一定要採用非阻塞read& write函數。

5.       條件觸發和邊緣觸發孰優孰劣

    邊緣觸發可以做到如下這點:

    “可以分離接收數據和處理數據的時間點。”

    條件觸發在輸入緩衝收到數據的情況下,如果不讀取(延遲處理),則每次調用epoll_wait函數時都會產生相應事件,而且事件數也會累加,服務器端不能承受。

    從實現模型的角度看,邊緣觸發更有可能帶來高性能。



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