服务器编程需要处理大量链接,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,NULL);
//封装成任务,分发出去处理
}
比如什么都不做,只是出现读读写写处理异常的形式
while(true)
{
int ret = select(&read_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,NULL);
//封装成分发任务,分发出去处理
clear(&read_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,&event );
//or epoll_ctl(epoll_fd,EPOLL_CTL_ADD,&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