1. 项目简介:
模拟 muduo 库实现non-blocking + IO-multiplexing + loop线程模型的高并发 TCP 服务器模型。
2. 开发环境:
CentOS7 linux环境。
3. 技术栈:
C++、多线程、socket网络编程、epoll多路转接。
4. 项目设计:
整体采用non-blocking + IO-multiplexing + loop线程的设计框架,其中线程模型采用one loop per thread的多线程服务端网络编程模型,结合reactor模型进行实现。
重要组件:
Event事件、Reactor反应堆、Dumultiplex事件分发器、EventHandler事件处理器。
首先服务器将用户的所关心的Event事件以及事件发生后的Handler处理函数打包注册到Reactor反应堆上,由Reactor向Demultiplex事件分发器的Epoll添加、修改、删除Event,并且启动Reactor反应堆,开启事件循环;当有事件发生时,Demultiplex就会通知Reactor,此时Reactor就会调用服务器事先注册的Event所对应的EventHandler去处理事件。
logger组件顾名思义,就是对于日志信息的封装,主要作用就是给用户提供一些提示信息,如,登陆成功等;还有就是当代码量上去了之后,不可避免的会出现一些编码的错误,此时日志信息就可以给我提供一些错误信息,方便我们进行调试。其主要包含以下模块:
channel可以理解为通道,对应到reactor模型中即为Event。它封装了用于网络通信的sockfd、sockfd所对应的用户感兴趣的事件events,以及存储发生事件的revents。主要实现了以下功能:
也就是说,channel其实就是将Event以及Handler打包后的产物,用sockfd进行标识,同时提供了一些函数接口,便于后续操作。
Poller组件是多路事件分发器的核心,在muduo库中,poller组件其实就是对poll和epoll的一个抽象,在实现上采用多态机制,为poll和epoll提供的抽象类,poll和epoll通过继承poller并重写poller所提供的纯虚函数接口,以实现多路转接模型。它封装了sockfd以及指向sockfd所打包的channel的指针,以sockfd为键,以channel*为值存储到无序关联容器unordered_map中。并且给派生类即EpollPoller提供了可供其重写的纯虚函数:
EpollPoller组件通过继承Poller并且重写其提供的纯虚函数接口,实现epoll多路转接模型。EpollPoller其实就是将epoll的接口封装成了一个类,提供向epoll中添加、修改、删除所关心的事件的接口以及开启事件监听的函数接口,并且对外还提供了向用户返回发生事件的接口以及更新channel的接口。
Poller和EpollPoller对应的reactor模型中即就是Demultiplex事件分发器。
EventLoop组件即为事件循环,对应的reactor模型中即就是Reactor反应堆,主要的功能就是:
Thread组件顾名思义就是线程,所以其实现的功能主要也都是与线程相关的:
EventLoopThread事件循环线程,是对Thread组件的一层封装,主要功能就是提供启动底层的线程(调用Thread所提供的创建线程的方法)并且为将要创建的线程设置入口函数。
EventLoopThreadPool组件为事件循环线程池,主要功能是:
Socket组件是对TCP网络编程的一个抽象,将TCP网络编程的函数接口抽象成了Socket类,主要的功能就是:
Acceptor组件是在Socket组件的基础上进行了封装,主要功能:
buffer组件是网络库底层的数据缓冲区,主要解决的问题就是当TCP接收缓冲区较小但是用户需发送数据过多或者TCP发送数据过多但是我们只需要读取其中一部分数据,未发送的数据或者未读取的数据的存储问题。它主要实现的功能就是:
TcpConnection组件主要做于新用户连接后的一系列回调操作。一个连接成功的客户端对应一个TcpConnection,其主要封装了连接成功的用户的socket,Channel,当有读写事件发生时,对发送缓冲区和接收缓冲区进行操作,并且为当前连接的用户设置各种的回调操作。
TcpServer组件是所有组件的总调度者,供用户创建TcpServer对象,使用TCP服务器的,它对外提供了设置建立新连接回调函数、读写消息回调函数、开启事件循环、设置线程数量并创建线程等函数接口,对内提供并绑定了有新用户连接时的回调函数,通过轮询算法分发新连接用户到EventLoopThread上(即subLoop)。并且设置了关闭连接的回调函数。它主要封装了Acceptor以及EventLoopThreadPool。
用户建立TCP服务器,在创建TcpServer对象时,其构造函数会创建Acceptor对象以及EventLoopThreadPool对象,并且通过Acceptor的setNewConnectionCallback方法为Acceptor对象中所封装的acceptChannel绑定有新连接到来时的回调函数。
其中Acceptor对象的构造函数会做以下三件事:
当用户调用start()方法启动服务后,EventLoopThreadPool对象就会调用自己的start方法创建用户所设置的threadnum个线程,并且启动所创建的线程。Acceptor对象调用自己的listen方法做以下两件事:
用户调用所创建的事件循环对象的loop方法,开启事件循环。在loop方法中,会通过EventLoop所封装的poller对象调用EpollPoller的poll方法,即调用epoll_wait开始监听注册的事件。此时服务器的服务就启动完成了。
当有新用户连接时,当前loop对象底层的EpollPoller就会监听到acceptChannel的读事件发生,进而调用构造Acceptor对象时绑定的回调函数,在回调函数中,会做以下两件事:
处理新连接的回调函数所做事情:
当用户有读事件发生时,底层的EpollPoller就会将发生的事件上报给EventLoop,EventLoop此时就会调用事先给channel所绑定的读事件的回调函数进行处理。而这个回调函数是用户通过TcpServer的setMessageCallback方法设置的,TcpServer在处理新连接的回调函数中构造TcpConnection对象时,通过TcpConnection所提供的setReadCallback将其绑定到TcpConnection对象所对应的channel中的。所以最终相应到了用户所设置的可读写事件的回调函数上。
当连接断开时,底层的EpollPoller就会将发生的事件上报给EventLoop,EventLoop此时就会调用事先给channel所绑定的关闭连接事件的回调函数进行处理。TcpConnection所做的事情就是删除该连接以及将所对应的sockfd以及channel从Poller的无序管理容器中删除,并且将注册在EpollPoller上的事件全部删除。