epoll原理
n = epoll_wait(epfd,events,20,500);
for(i=0;i<n;++i)
{
c = event_list[i].data.ptr;
c = (ngx_connection_t *) ((uintptr_t) c & (uintptr_t) ~3);
/* 連接有可讀事件,且該讀事件是active活躍的 */
if ((revents & EPOLLIN) && rev->active) {
rev = c->read;
/*
* 這裏要區分active與ready:
* active是指事件被添加到epoll對象的監控中,
* 而ready表示被監控的事件已經準備就緒,即可以對其進程IO處理;
* 讀事件的ready代表有數據可以接受處理,寫事件的ready代表buffer有空間可寫
*/
rev->ready = 1;
/*
* NGX_POST_EVENTS表示已準備就緒的事件需要延遲處理,
* 根據accept標誌位將事件加入到相應的隊列中;
*/
if (flags & NGX_POST_EVENTS) {
queue = rev->accept ? &ngx_posted_accept_events
: &ngx_posted_events;
ngx_post_event(rev, queue);
} else {
/* 若不延遲處理,則直接調用事件的處理函數 */
rev->handler(rev);
}
}
}
// write事件同理
}
每個event在rev→ready設置爲1之後,需要進行io處理。
ssize_t
ngx_unix_send(ngx_connection_t *c, u_char *buf, size_t size)
{
...
for ( ;; ) {
n = send(c->fd, buf, size, 0);
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"send: fd:%d %z of %uz", c->fd, n, size);
if (n > 0) {
if (n < (ssize_t) size) {
// 發送的數據大小大於0,小於n,說明未發送完成,wev->ready置爲0,此次發送結束,下次繼續調用write事件進行發送。
wev->ready = 0;
}
c->sent += n;
return n;
}
}
...
}
ssize_t
ngx_unix_recv(ngx_connection_t *c, u_char *buf, size_t size)
{
...
do {
n = recv(c->fd, buf, size, 0);
ngx_log_debug3(NGX_LOG_DEBUG_EVENT, c->log, 0,
"recv: fd:%d %z of %uz", c->fd, n, size);
if (n == 0) {
// 無可接受數據,此次讀事件結束,ready置0
rev->ready = 0;
rev->eof = 1;
}
...
}
每次的讀事件,是該fd的讀buffer上有數據被寫入,引發epollin,如果是水平觸發,只要有數據就會一直觸發這個讀事件,如果是邊緣觸發,就會等到新的數據被寫入纔會觸發該事件,因此在面對高性能的邊緣觸發中,一定要注意數據的接收,每次用while true讀完,不然一旦有殘留,需要等下個包到達才能取出。
每次的寫事件很難被觸發,因爲正常都會被直接發送完成,只有發送一部分數據,寫buffer就滿了的情況下,epoll會監聽該事件,一旦寫隊列緩衝區有空間,可寫,纔會觸發新的寫事件,將數據發送完成。