1. 程式人生 > >Reactor模式 和epoll

Reactor模式 和epoll

循環 reactor sched man 存儲 ack oot yep concrete

技術分享


a, Handle表示句柄,文件描述符、socket等;
b, EventDemultiplexer表示多路分發機制,調用系統提供的多IO路復用,比如select,epoll。
程序先將關註的句柄註冊到EventDemultiplexer,當有相關事件到來觸發EventDemultiplexer通知程序。
c, EventHandler定義事件處理方法,
d, Reactor是事件管理的接口,註冊和銷毀事件,並運行事件循環,當EventDemultiplexer返回Handle有事件"就緒",將其分發給EventHandler上對應的方法。
e, ConcreteEventhandler實現每個事件的處理邏輯。




epoll原理:
struct eventpoll{
/*紅黑樹的根節點,這顆樹中存儲著所有添加到epoll中的需要監控的事件*/
struct rb_root rbr;
/*雙鏈表中則存放著將要通過epoll_wait返回給用戶的滿足條件的事件*/
struct list_head rdlist;
...
};
每一個epoll
對象都有一個獨立的eventpoll結構體,當我們執行epoll_ctl時,除了把socket放到epoll文件系統裏file對象對應的紅黑樹上之外,還會給內核中斷處理程序註冊一個回調函數,告訴內核,如果這個句柄的中斷到了,就把它放到準備就緒list鏈表裏。

對於每一個事件,都會建立一個epitem結構體
struct epitem{
struct rb_node rbn;//紅黑樹節點
struct list_head rdllink;//雙向鏈表節點
struct epoll_filefd ffd; //事件句柄信息
struct eventpoll *ep; //指向其所屬的eventpoll對象
struct epoll_event event; //期待發生的事件類型
}
技術分享


epoll_wait在for循環中檢查epitem中有沒有已經完成的事件,有的話就把結果返回。沒有的話調用schedule_timeout()進入休眠,直到進程被再度喚醒或者超時。


LT模式下,只要一個句柄上的事件一次沒有處理完,會在以後調用epoll_wait時次次返回這個句柄,而ET模式僅在第一次返回。
當一個socket句柄上有事件時,內核會把該句柄插入上面所說的準備就緒list鏈表,這時我們調用epoll_wait,會把準備就緒的socket拷貝到用戶態內存,然後清空準備就緒list鏈表,最後,epoll_wait幹了件事,就是檢查這些socket,如果不是ET模式(就是LT模式的句柄了),並且這些socket上確實有未處理的事件時,又把該句柄放回到剛剛清空的準備就緒鏈表了。


struct
epoll_event ev,events[20]; //ev用於註冊事件,數組用於回傳要處理的事件
ev.data.fd=listenfd; //設置與要處理的事件相關的文件描述符
ev.events=EPOLLIN|EPOLLET; //設置要處理的事件類型

epfd=epoll_create(256);
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);



for( ; ; ) {
nfds = epoll_wait(epfd,events,20,500);
for(i=0;i<nfds;++i)
{
if(events[i].data.fd==listenfd) //有新的連接
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen); //accept這個連接
ev.data.fd=connfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev); //將新的fd添加到epoll的監聽隊列中
}
else if( events[i].events&EPOLLIN ) //接收到數據,讀socket
{
sockfd=events[i].data.fd;
n = read(sockfd, line, MAXLINE)) < 0 //讀
ev.data.ptr = md; //md為自定義類型,添加數據
ev.events=EPOLLOUT|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改標識符,等待下一個循環時發送數據,異步處理的精髓
}
else if(events[i].events&EPOLLOUT) //有數據待發送,寫socket
{
struct myepoll_data* md = (myepoll_data*)events[i].data.ptr; //取數據
sockfd = md->fd;
send( sockfd, md->ptr, strlen((char*)md->ptr), 0 ); //發送數據
ev.data.fd=sockfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev); //修改標識符,等待下一個循環時接收數據
}
else
{
//其他的處理
}
}
}



Reactor模式 和epoll