服務器編程需要處理大量鏈接,reactor模型是一種高效的事件處理模型,常常用於處理這種問題,其核心是一個不斷查詢的循環,查詢多個可能發生事件的事件源
while(true)
{
//查詢事件源是否有事件
}
爲了同時能夠處理大量鏈接,一般會使用多路複用I/O來監聽,比如epoll或select。
while(true)
{
int ret = select(&read_fds, &write_fds, &exp_fds, NULL);
}
監聽到準備好的I/O事件之後就需要分別處理掉,也就是所謂的分發,畢竟讀任務,寫任務,異常都需要不同的處理。
while(true)
{
int ret = select(&read_fds, &write_fds, &exp_fds, NULL);
//封裝成任務,分發出去處理
}
比如什麼都不做,只是出現讀讀寫寫處理異常的形式
while(true)
{
int ret = select(&read_fds, &write_fds, &exp_fds, NULL);
for_each(fd in read_fds)
{
do_read(fd);
}
for_each(fd in write_fds)
{
do_write(fd);
}
for_each(fd in exp_fds)
{
do_excep(fd);
}
}
通常在do_*函數裏面就幹自己想要處理的動作。
在每一次 while(true) 循環的最後會清理掉已經發生過事件的事件源,並重新註冊下一次需要監聽的事件源。因此在do_*函數裏面最後一件事情就是決定還要不要繼續監聽這次處理過的fd,也既是重新註冊回去。
while(true)
{
int ret = select(&read_fds, &write_fds, &exp_fds, NULL);
//封裝成分發任務,分發出去處理
clear(&read_fds, &write_fds, &exp_fds);
re_register();
}
用epoll的ET模式會省事兒很多,觸發一次之後就不再觸發,除非再次註冊。
while(true)
{
int ret = epoll_wait(epoll_fd, events, MAX_EVENTS, -1);
for(int i = 0; i < ret; ++i)
{
if(events[i].fd == listenfd)
{
//添加新進入的socket
}
if(events[i].events & EPOLLIN)
{
//讀數據,然後封裝成任務分發處理
}
else if(events[i].events & EPOLLOUT)
{
//寫數據
}
}
}
void process()
{
//處理數據
epoll_ctl(epoll_fd, EPOLL_CTL_DEL, fd, 0 );
//or epoll_ctl(epoll_fd, EPOLL_CTL_MOD, fd, &event );
//or epoll_ctl(epoll_fd, EPOLL_CTL_ADD, fd, &event );
}
單線程的reactor分發後的處理也在同一個線程中,需要處理比較迅速,絕對不能阻塞,否則整個事件循環就阻塞了,這是個悲劇。
多線程的reactor會將任務分發到不同的線程中去處理,最大化利用多核cpu。可以利用線程池來管理這些線程,線程池持有一個任務隊列,各個線程競爭的從任務隊列裏面領取任務來處理。在多個線程中調用epoll_ctl和epoll_wait是線程安全的。
更多學習:
C語言reactor模式參考libevent
python語言reactor模式參考twisted
參考資料:
《Linux高性能服務器編程》
http://www.cnblogs.com/hustcat/archive/2012/01/11/2319249.html