重写muduo库

概述

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模型进行实现。

Reactor模型

重要组件:
Event事件、Reactor反应堆、Dumultiplex事件分发器、EventHandler事件处理器。
Reactor模型图
首先服务器将用户的所关心的Event事件以及事件发生后的Handler处理函数打包注册到Reactor反应堆上,由Reactor向Demultiplex事件分发器的Epoll添加、修改、删除Event,并且启动Reactor反应堆,开启事件循环;当有事件发生时,Demultiplex就会通知Reactor,此时Reactor就会调用服务器事先注册的Event所对应的EventHandler去处理事件。

整体框架

在这里插入图片描述

组件详解

logger组件

logger组件顾名思义,就是对于日志信息的封装,主要作用就是给用户提供一些提示信息,如,登陆成功等;还有就是当代码量上去了之后,不可避免的会出现一些编码的错误,此时日志信息就可以给我提供一些错误信息,方便我们进行调试。其主要包含以下模块:

  1. LOG_INFO:提示信息。
  2. LOG_ERROR:错误信息(不影响程序正常执行)。
  3. LOG_FATAL:致命错误信息(导致程序崩溃)。
  4. LOG_DUBUG:调试信息。

channel组件

channel可以理解为通道,对应到reactor模型中即为Event。它封装了用于网络通信的sockfd、sockfd所对应的用户感兴趣的事件events,以及存储发生事件的revents。主要实现了以下功能:

  1. 更新epoll中用户所关心的事件。
  2. 采用bind绑定器和functional函数对象机制提供了设置用户关心事件发生后的回调处理函数。
  3. 当有用户感兴趣的事件发生时,根据发生事件的不同,调用用户事先设定的回调函数,处理事件。

也就是说,channel其实就是将Event以及Handler打包后的产物,用sockfd进行标识,同时提供了一些函数接口,便于后续操作。

Poller & EpollPoller组件

Poller组件

Poller组件是多路事件分发器的核心,在muduo库中,poller组件其实就是对poll和epoll的一个抽象,在实现上采用多态机制,为poll和epoll提供的抽象类,poll和epoll通过继承poller并重写poller所提供的纯虚函数接口,以实现多路转接模型。它封装了sockfd以及指向sockfd所打包的channel的指针,以sockfd为键,以channel*为值存储到无序关联容器unordered_map中。并且给派生类即EpollPoller提供了可供其重写的纯虚函数:

  1. 开启事件监听。
  2. 更新epoll中用户所关心事件。
  3. 删除channel。

EpollPoller组件

EpollPoller组件通过继承Poller并且重写其提供的纯虚函数接口,实现epoll多路转接模型。EpollPoller其实就是将epoll的接口封装成了一个类,提供向epoll中添加、修改、删除所关心的事件的接口以及开启事件监听的函数接口,并且对外还提供了向用户返回发生事件的接口以及更新channel的接口。
Poller和EpollPoller对应的reactor模型中即就是Demultiplex事件分发器。

EventLoop组件

EventLoop组件即为事件循环,对应的reactor模型中即就是Reactor反应堆,主要的功能就是:

  1. 开启事件循环。
  2. 对EpollPoller以及channel进行操作,穿插在EpollPoller和channel之间,channel调用EventLoop提供的函数接口将用户所感兴趣的事件注册到epoll上,还可以通过EventLoop对用户所感兴趣的事件在epoll上进行更新、删除等操作,当有事件发生时,EpollPoller就会将所发生的事件通过参数反馈给EventLoop,EventLoop再调用用户预先在channel中设置的回调函数EventHandler对发生事件进行处理。
  3. 如果当前所发生的事件是运行在其他线程上的loop的所监听的事件,就需要唤醒阻塞epoll_wait上的线程,执行相应的EventHandler操作。通过系统调用接口eventfd创建一个wakeupFd,并打包成channel注册到所在线程的EpollPoller中,如果调用回调的loop所属线程不是当前线程,那么就向这个loop所属的wakeupFd对应的事件中写入数据,用以唤醒loop所在的线程执行相应的回调。

Thread & EventLoopThread & EventLoopThreadPool

Thread组件

Thread组件顾名思义就是线程,所以其实现的功能主要也都是与线程相关的:

  1. 创建线程并且执行线程入口函数。
  2. 设置线程等待。

EventLoopThread组件

EventLoopThread事件循环线程,是对Thread组件的一层封装,主要功能就是提供启动底层的线程(调用Thread所提供的创建线程的方法)并且为将要创建的线程设置入口函数。

EventLoopThreadPool组件

EventLoopThreadPool组件为事件循环线程池,主要功能是:

  1. 设置线程数量threadnum。
  2. 创建用户所设置的threadnum个线程,并且将每个线程上所创建的loop的地址返回。
  3. 通过轮询算法选择一个loop,将新连接用户所关心的channel分发给loop。

Socket & Acceptor组件

Socket组件

Socket组件是对TCP网络编程的一个抽象,将TCP网络编程的函数接口抽象成了Socket类,主要的功能就是:

  1. 创建套接字。
  2. 绑定地址信息。
  3. 监听新连接的到来。
  4. 获取新连接用于网络通信的套接字socket。

Acceptor组件

Acceptor组件是在Socket组件的基础上进行了封装,主要功能:

  1. 创建用于监听和获取新连接的accpetSocket,为其绑定地址信息。
  2. 将accpetSocket以及其感兴趣的事件(新连接到来)打包成accpetChannel。
  3. 开启监听新连接到来并将accpetChannel通过EventLoop注册到所属的epoll中去。
    其实acceptorChaneel所属的loop就是mainLoop。

buffer组件

buffer组件是网络库底层的数据缓冲区,主要解决的问题就是当TCP接收缓冲区较小但是用户需发送数据过多或者TCP发送数据过多但是我们只需要读取其中一部分数据,未发送的数据或者未读取的数据的存储问题。它主要实现的功能就是:

  1. 将未发送的数据或者未读取的数据存储下来。
  2. 对缓冲区进行扩容。
  3. 向调用发返回读、写缓冲区的大小。
    其模型为:
    在这里插入图片描述

TcpConnection组件

TcpConnection组件主要做于新用户连接后的一系列回调操作。一个连接成功的客户端对应一个TcpConnection,其主要封装了连接成功的用户的socket,Channel,当有读写事件发生时,对发送缓冲区和接收缓冲区进行操作,并且为当前连接的用户设置各种的回调操作。

  1. 通过C++11从boost库中引入的bind绑定器和functional函数对象机制,为当前连接所对应的channel设置各种事件的回调函数。
  2. 实现EventHandler事件发生。
  3. 发送数据。

TcpServer组件

TcpServer组件是所有组件的总调度者,供用户创建TcpServer对象,使用TCP服务器的,它对外提供了设置建立新连接回调函数、读写消息回调函数、开启事件循环、设置线程数量并创建线程等函数接口,对内提供并绑定了有新用户连接时的回调函数,通过轮询算法分发新连接用户到EventLoopThread上(即subLoop)。并且设置了关闭连接的回调函数。它主要封装了Acceptor以及EventLoopThreadPool。

项目各模块交互流程梳理

用户使用muduo库编写服务器程序的流程

  1. 编写TcpServer类:在构造函数中绑定连接建立和断开的回调函数以及可读写事件的回调函数,设置线程个数(一般和CPU核心数相同),对外start()提供启动服务的接口,实现连接建立和断开的回调函数以及可读写事件的回调函数。
  2. 编程main函数:创建事件循环对象,提供InetAddress地址信息,创建TcpServer服务器对象,启动服务,开启事件循环。

TCP服务器建立

用户建立TCP服务器,在创建TcpServer对象时,其构造函数会创建Acceptor对象以及EventLoopThreadPool对象,并且通过Acceptor的setNewConnectionCallback方法为Acceptor对象中所封装的acceptChannel绑定有新连接到来时的回调函数。
其中Acceptor对象的构造函数会做以下三件事:

  1. 创建用于监听新用户到来的监听套接字listenfd,绑定地址信息。
  2. 将listenfd和监听新用户连接事件打包成acceptChannel。
  3. 设置事件发生后的回调函数。

当用户调用start()方法启动服务后,EventLoopThreadPool对象就会调用自己的start方法创建用户所设置的threadnum个线程,并且启动所创建的线程。Acceptor对象调用自己的listen方法做以下两件事:

  1. 调用Sockets所封装的listen方法开始监听新连接的到来;
  2. 调用acceptChannel的enableReading方法,将listenfd所关心的事件注册到EpollPoller上。

用户调用所创建的事件循环对象的loop方法,开启事件循环。在loop方法中,会通过EventLoop所封装的poller对象调用EpollPoller的poll方法,即调用epoll_wait开始监听注册的事件。此时服务器的服务就启动完成了。

新用户的连接

当有新用户连接时,当前loop对象底层的EpollPoller就会监听到acceptChannel的读事件发生,进而调用构造Acceptor对象时绑定的回调函数,在回调函数中,会做以下两件事:

  1. 调用Acceptor所封装的acceptSocket_的accept函数获取新连接用于通信的套接字sockfd以及地址信息。
  2. 调用构造TcpServer时绑定的处理新连接的回调函数,并将新连接用于通信的套接字以及地址信息传给该回调函数。

处理新连接的回调函数所做事情:

  1. 根据轮询算法选择一个subLoop。
  2. 获取sockfd对应的地址信息。
  3. 创建TcpConnection对象,把选择的subLoop绑定到TcpConnection所属线程上,将用户设置的连接建立和断开的回调函数以及可读写事件等回调函数通过TcpConnection对象所提供的方法设置给当前TcpConnection连接,并且设置服务器内部关闭连接的回调函数。
  4. 执行TcpConnection对象建立连接的回调函数(唤醒TcpConnection所属线程,建立连接回调函数)。在建立连接回调函数中,向EpollPoller中注册当前sockfd所打包channel的读写事件,并且执行用户设置的连接建立和断开的回调函数。此时,连接就建立成功了。
    在这里插入图片描述

数据通信

当用户有读事件发生时,底层的EpollPoller就会将发生的事件上报给EventLoop,EventLoop此时就会调用事先给channel所绑定的读事件的回调函数进行处理。而这个回调函数是用户通过TcpServer的setMessageCallback方法设置的,TcpServer在处理新连接的回调函数中构造TcpConnection对象时,通过TcpConnection所提供的setReadCallback将其绑定到TcpConnection对象所对应的channel中的。所以最终相应到了用户所设置的可读写事件的回调函数上。

关闭连接

当连接断开时,底层的EpollPoller就会将发生的事件上报给EventLoop,EventLoop此时就会调用事先给channel所绑定的关闭连接事件的回调函数进行处理。TcpConnection所做的事情就是删除该连接以及将所对应的sockfd以及channel从Poller的无序管理容器中删除,并且将注册在EpollPoller上的事件全部删除。

  • 0
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值