
在高并发网络服务开发中Reactor事件驱动思想是解决高并发IO问题的核心方法配合Linux高性能IO多路复用epoll的ET模式演进为主从Reactor多线程架构构成了Muduo、大型网关、分布式服务的底层核心架构。今天来给大家讲讲这个主从Reactor架构epoll-ET架构。在Linux高并发网络编程领域epoll是离不开的核心技术主要是通过部分读部分写epollET模式阻塞非阻塞这三者来实现先来详细讲讲这三者。部分写部分读read系统调用的流程为进入内核态根据fd找到对应的文件对象Linux读写文件几乎都走页缓存不会直接碰磁盘如果内核缓冲区有东西就直接用没有东西才去磁盘读入内核缓冲区接下来需要把数据从内核缓冲区拷贝到用户缓冲区两块内存完全独立一次能拷多少拷多少这也是最需要注意的地方read的部分读正是发生在内核缓冲区到用户buf这一步原因是内核缓冲区和用户缓冲区大小限制可能内核缓冲区数据不够读也可能内核缓冲区数据太多用户buf装不下。write也是一样。epollET模式epoll分为水平模式和边缘模式水平模式LT简单来说就是缓冲区只要有数据就一直通知缓冲区可读或可写边缘模式简单来说是在缓冲区状态变化的那一刻只通知一次。LT的时候就算出现部分写/部分读也没关系下次还会通知但是只要缓冲区状态满足内核就一直发事件用户态/内核态切换频繁开销大ET由于内核只通知一次在高并发连接时比LT高效的多但是如果出现部分读没读完会出大问题所以就引出了epollET模式的核心循环读取循环读取当read返回-1并且errnoEAGAIN/EWOULDBLOCK时说明内核缓冲区已经空了读完了。阻塞非阻塞我们在实现高并发服务器时通常用epollET模式非阻塞接下来说说什么是阻塞什么是非阻塞阻塞就是当内核缓冲区为空的时候read不返回一直等待有数据可读了非阻塞就是当读到内核缓冲区没有数据可读时直接返回-1不等待阻塞和非阻塞的区别在哪当内核缓冲区有数据的时候(不管数据够不够)行为完全一样当内核缓冲区完全没有数据的时候二者非为等待和立刻返回。为什么说epollET模式必须搭配非阻塞fd使用原因就在于我们要检查是否内核缓冲区已经空了假如我们用阻塞fd,当内核缓冲区此刻为空read就不会返回了ET非阻塞“卡死”所以得用非阻塞fd当检测到内核缓冲区为空的时候read返回-1并设置errno,只需要在while循环里检查errno并break就可以了。最后为大家赋上一小段核心代码void set_nonblock(int fd) {//设置fd为非阻塞 int flags fcntl(fd, F_GETFL, 0); fcntl(fd, F_SETFL, flags | O_NONBLOCK); } while(1){ n read(fd, buf, sizeof(buf)); if(n0){ clients[fd].readbuf.append(buf, n); }else if(n0){ close(fd); clients.erase(fd); del_epoll(fd, 0); return; }else { if(errnoEAGAIN||errnoEWOULDBLOCK){ break;//此刻内核缓冲区已经为空读完了 }else{ close(fd); return; } } }现在开始讲Reator,很多开发者会混淆epoll和Reactor,epoll是系统调用Reactor是一套事件驱动的设计思想。Reactor核心思想不轮询、不阻塞等待连接、由IO多路复用监听所有文件描述符只在事件就绪的时候分发任务去处理。核心载体是epollET这是实现Reactor的关键工具前面说的实现高并发服务器就是用Reactor思想。主从Reactor架构主线程也就是主Reactor只负责监听是否有客户端连接只处理连接不做读写业务处理读写业务处理扔给子线程(子Reactor)主线程每处理一个新客户端连接事件后把客户端fd挂载到子线程的从Reactor子线程有自己独立的epoll加事件循环负责监听与处理已连接客户端fd的 可读 / 可写 / 异常事件。整套服务器监听处理逻辑主线程epoll只注册listen_fd的ET读事件当监听到有新客户端连接时触发listen_fd的读事件主线程拿到listen_fd设置为非阻塞将新客户端fd挂载到某一个子线程子线程内部epoll注册这个客户端fd监听读事件后续这个客户端的读写事件都由这个子Reactor处理