事件驅動模型
事件驅動模型
滑鼠的一個點擊,移動,鍵盤的按鍵按下等等操作,都是對應操作系統的一個事件,然後應用程序接受你的操作進行處理
introduction
通常,我們寫伺服器處理模型的程序時,有以下幾種模型:
(1)每收到一個請求,創建一個新的進程,來處理該請求;
(2)每收到一個請求,創建一個新的線程,來處理該請求;
(3)每收到一個請求,放入一個事件列表,讓主進程通過非阻塞I/O方式來處理請求
上面的幾種方式,各有千秋,
第(1)種方法,由於創建新的進程的開銷比較大,所以,會導致伺服器性能比較差,但實現比較簡單。
第(2)種方式,由於要涉及到線程的同步,有可能會面臨死鎖等問題。
第(3)種方式,在寫應用程序代碼時,邏輯比前面兩種都複雜。
綜合考慮各方面因素,一般普遍認為第(3)種方式是大多數網路伺服器採用的方式,這也是本文討論的重點—事件驅動處理庫。
select在linux和windows平台上都支持的,介面基本上相同,但參數的含義略有不同。通常,使用select庫的步驟是:
(1)創建所關注的事件的描述符集合(fd_set),對於一個描述符,可以關注其上面的讀(read)、寫(write)、異常(exception)事件,所以通常,要創建三個fd_set,一個用來收集關注讀事件的描述符,一個用來收集關注寫事件的描述符,另外一個用來收集關注異常事件的描述符集合。
(2)調用select(),等待事件發生。這裡需要注意的一點是,select的阻塞與是否設置非阻塞I/O是沒有關係的。select的原型如下所示:
int select(int nfds ,
fd_set* readfds ,
fd_set* writefds ,
fd_set* exceptfds,
const struct timeval* timeout
其中,最後一個參數timeout,可以設置select等待的時間。如果該值設置為0,那麼,select()在有事件發生的時候就立即返回。如果該值不為0,那麼,select()會等待指定的時間,然後再返回。select()的返回值指定了發生事件的fd個數。
(3)輪詢所有fd_set中的每一個fd ,檢查是否有相應的事件發生,如果有,就進行處理。
poll庫是在linux2.1.23中引入的,windows平台不支持poll. poll與select的基本方式相同,都是先創建一個關注事件的描述符的集合,然後再去等待這些事件發生,然後再輪詢描述符集合,檢查有沒有事件發生,如果有,就進行處理。因此,poll有著與select相似的處理流程:
(1)創建描述符集合,設置關注的事件
(2)調用poll(),等待事件發生。下面是poll的原型:
int poll(struct pollfd * fds, nfds_t nfds, int timeout);
類似select,poll也可以設置等待時間,效果與select一樣。
(3)輪詢描述符集合,檢查事件,處理事件。
在這裡要說明的是,poll與select的主要區別在與,select需要為讀、寫、異常事件分別創建一個描述符集合,最後輪詢的時候,需要分別輪詢這三個集合。而poll只需要一個集合,在每個描述符對應的結構上分別設置讀、寫、異常事件,最後輪詢的時候,可以同時檢查三種事件。
epoll是和上面的poll和select不同的一個事件驅動庫,它是在linux 2.5.44中引入的,它屬於poll的一個變種。上面的poll和select庫,它們的最大的問題就在於效率。它們的處理方式都是創建一個事件列表,然後把這個列表發給內核,返回的時候,再去輪詢檢查這個列表,這樣在描述符比較多的應用中,效率就顯得比較低下了。一種比較好的做法是,把描述符列表交給內核,一旦有事件發生,內核把發生事件的描述符列表通知給進程,這樣就避免了輪詢整個描述符列表。epoll就是這樣一種模型。下面對epoll的使用進行說明:
(1).創建一個epoll描述符,調用epoll_create()來完成,epoll_create()有一個整型的參數size,用來告訴內核,要創建一個有size個描述符的事件列表(集合)
int epoll_create(int size)
(2).給描述符設置所關注的事件,並把它添加到內核的事件列表中去,這裡需要調用epoll_ctl()來完成。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event * event)
這裡op參數有三種,分別代表三種操作:
a. EPOLL_CTL_ADD, 把要關注的描述符和對其關注的事件的結構,添加到內核的事件列表中去
b. EPOLL_CTL_DEL,把先前添加的描述符和對其關注的事件的結構,從內核的事件列表中去除
c. EPOLL_CTL_MOD,修改先前添加到內核的事件列表中的描述符的關注的事件
(3). 等待內核通知事件發生,得到發生事件的描述符的結構列表,該過程由epoll_wait()完成。得到事件列表后,就可以進行事件處理了。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout)
在使用epoll的時候,有一個需要特別注意的地方,那就是epoll觸發事件的文件有兩種方式:
(1)Edge Triggered(ET),在這種情況下,事件是由數據到達邊界觸發的。所以要在處理讀、寫的時候,要不斷的調用read/write,直到它們返回EAGAIN,然後再去epoll_wait(),等待下次事件的發生。這種方式適用要遵從下面的原則:
a. 使用非阻塞的I/O;b.直到read/write返回EAGAIN時,才去等待下一次事件的發生。
(2)Level Triggered(LT), 在這種情況下,epoll和poll類似,但處理速度上可能比poll快。在這種情況下,只要有數據沒有讀、寫完,調用epoll_wait()的時候,就會有事件被觸發。