在高併發tcp請求中,為了實現資源的節省,效率的提公升,epoll逐漸替代了之前的select和poll,它在使用者層上規避了忙輪詢這種效率不高的監聽方式,epoll的時間複雜度為o(1), 也就意味著,epoll在高併發場景,隨著檔案描述符的增長,有良好的可擴充套件性。
關鍵函式有三個:
核心資料結構
typedef union epoll_data
epoll_data_t;
struct epoll_event
;
struct eventpoll ;我們在呼叫epoll_create時,核心除了幫我們在epoll檔案系統裡建了個file結點,在核心cache裡建了個紅黑樹用於儲存以後epoll_ctl傳來的socket外,還會再建立乙個rdllist雙向鍊錶,用於儲存準備就緒的事件,當epoll_wait呼叫時,僅僅觀察這個rdllist雙向煉表裡有沒有資料即可。有資料就返回,沒有資料就sleep,等到timeout時間到後即使鍊錶沒資料也返回。所以,epoll_wait非常高效。
所有新增到epoll中的事件都會與裝置(如網絡卡)驅動程式建立**關係,也就是說相應事件的發生時會呼叫這裡的**方法。這個**方法在核心中叫做ep_poll_callback,它會把這樣的事件放到上面的rdllist雙向鍊錶中。
在epoll中對於每乙個事件都會建立乙個epitem結構體,如下所示:
struct epitem ; // 這裡包含每乙個事件對應著的資訊。當呼叫epoll_wait檢查是否有發生事件的連線時,只是檢查eventpoll物件中的rdllist雙向鍊錶是否有epitem元素而已,如果rdllist鍊錶不為空,則這裡的事件複製到使用者態記憶體(使用共享記憶體提高效率)中,同時將事件數量返回給使用者。因此epoll_waitx效率非常高。epoll_ctl在向epoll物件中新增、修改、刪除事件時,從rbr紅黑樹中查詢事件也非常快,也就是說epoll是非常高效的。
epoll有epolllt和epollet兩種觸發模式,lt是預設的模式,et是「高速」模式。
lt(水平觸發)模式下,只要這個檔案描述符還有資料可讀,每次 epoll_wait都會返回它的事件,提醒使用者程式去操作;
et(邊緣觸發)模式下,在它檢測到有 i/o 事件時,通過 epoll_wait 呼叫會得到有事件通知的檔案描述符,對於每乙個被通知的檔案描述符,如可讀,則必須將該檔案描述符一直讀到空,讓 errno 返回 eagain 為止,否則下次的 epoll_wait 不會返回餘下的資料,會丟掉事件。如果et模式不是非阻塞的,那這個一直讀或一直寫勢必會在最後一次阻塞。
還有乙個特點是,epoll使用「事件」的就緒通知方式,通過epoll_ctl註冊fd,一旦該fd就緒,核心就會採用類似callback的**機制來啟用該fd,epoll_wait便可以收到通知。
【總結】:
et模式(邊緣觸發)只有資料到來才觸發,不管快取區中是否還有資料,緩衝區剩餘未讀盡的資料不會導致epoll_wait返回;
lt 模式(水平觸發,預設)只要有資料都會觸發,緩衝區剩餘未讀盡的資料會導致epoll_wait返回。
【epoll反應堆模型的流程】:
epoll_create(); // 建立監聽紅黑樹epoll_ctl(); // 向書上新增監聽fd
epoll_wait(); // 監聽
有客戶端連線上來--->lfd呼叫acceptconn()--->將cfd掛載到紅黑樹上監聽其讀事件--->
epoll_wait()返回cfd--->cfd**recvdata()--->將cfd摘下來監聽寫事件--->
epoll_wait()返回cfd--->cfd**senddata()--->將cfd摘下來監聽讀事件--->...--->
/*
epoll基於非阻塞i/o事件驅動
*/#include #include #include #include #include #include #include #include #include #include #define max_events 1024
#define buflen 4096
#define serv_port 8080
void recvdata(int fd ,int events , void*arg);
void senddata(int fd ,int events , void*arg);
struct myevent_s
;int g_efd; //全域性變數 記錄epoll_create返回的檔案描述符
struct myevent_s g_events[max_events+1]; //自定義結構體型別陣列
//初始化結構體myevent_s成員變數
void eventset(struct myevent_s *ev , int fd, void (*call_back)(int, int ,void*),void *arg)
ev->last_active= (time)null;
return;
}/*向監聽紅黑樹新增乙個檔案描述符*/
void event_add(int efd , int events , struct myevent_s *ev)};
int op;
epv.events = ev->events = events;
epv.data.ptr = ev;
if(ev->status == 0 )
if(epoll_ctl(efd,op,ev->fd,&epv)<0)
else
printf("epv add succeed\n");
}void eventdel(int efd ,struct myevent_s *ev)};
if(ev->status != 1)
epv.data.ptr = null;
ev->status = 0;
epoll_ctl(efd,epoll_ctl_del,ev->fd, &epv);
return ;
}void acceptconn(int lfd , int events ,void *arg)
else
doelse if(len == 0)
else
}void senddata(int fd ,int events , void *arg)
else
}void initlistensocket(int efd , short port)
int main()
initlistensocket(g_efd,port); //初始化監聽socket
struct epoll_event events[max_events+1]; //儲存已經滿足就緒事件的檔案描述符陣列 為epoll_wait做準備
printf("server running:port[%d]\n", port);
int checkpos = 0 ,i;
while (1)
long duration = now - g_events[checkpos].last_active;
if(duration >= 60)
}*///監聽紅黑樹g_efd,將滿足的事件新增到g_events中。
int nfd = epoll_wait(g_efd,events,max_events+1,1000);
if(nfd<0)
for(i = 0 ;i < nfd ;i++)
if((events[i].events & epollout) && (ev->events & epollout))}}
}
使用方法:
gcc server.c -o server
./server
重新開啟新的終端:
nc 127.1 8080
epoll反應堆模型
參考 參考 求職期間,還是得好好學習。看了大概的思路,自己理解著敲一下。和原來 的有些地方不同。include include include include include include include include include define max events 1024 監聽上限 de...
epoll反應堆理解
反應堆伺服器 接收客戶端的資訊,然後傳送回去 監聽到事件才做對應的 函式,這是reactor乙個固定的正規化,不能太隨心所欲.當然可以在readdata裡write client fd 但不符合正規化.而且,回想一下epoll的作用 幫助程序監聽檔案描述符 是否可寫或可讀,只有epoll給你監聽到可...
reactor 反應堆模型
1.reactor模型,本質上講管理網路io 使用下面這個結構用來管理我們的io struct ntyreactor 下面這段 比較核心 我們關心的事件與發生的事件發生時,才去呼叫 2.實現 include include include include include include includ...