服务器编程需要处理大量链接,reactor模型是一种高效的事件处理模型,常常用于处理这种问题,其核心是一个不断查询的循环,查询多个可能发生事件的事件源
@H_404_3@while(true) { //查询事件源是否有事件 }为了同时能够处理大量链接,一般会使用多路复用I/O来监听,比如epoll或select。
@H_404_3@while(true) { int ret = select(&read_fds,&write_fds,&exp_fds,NULL); }监听到准备好的I/O事件之后就需要分别处理掉,也就是所谓的分发,毕竟读任务,写任务,异常都需要不同的处理。
@H_404_3@while(true) { int ret = select(&read_fds,NULL); //封装成任务,分发出去处理 }比如什么都不做,只是出现读读写写处理异常的形式
@H_404_3@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,也既是重新注册回去。
用epoll的ET模式会省事儿很多,触发一次之后就不再触发,除非再次注册。
@H_404_3@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