muduo库中各个类的简明介绍

1. base

1.1 Timestamp时间戳类

在这里插入图片描述

用于实现获取当前的时间等功能

1.2 AtomicIntegerT模板类

在这里插入图片描述

用于以原子操作的形式实现某个类的自增、自减、add等操作

1.3 Types模板类

用于实现内置类型的隐式转换以及基类指针向下转换为子类指针

1.4 Exception异常类

在这里插入图片描述

用于记录异常信息,并提供返回异常信息的接口

1.5 Thread线程类

在这里插入图片描述

muduo库中所有的线程都是该类的实例化对象,一个对象包含tid、name_、线程建立时的回调函数func等成员

1.6 互斥量类

1.6.1 MutexLock和MutexLockGuard互斥量类

在这里插入图片描述
在这里插入图片描述

用RAII技法实现的Mutex互斥量类,在MutexLockGuard类的对象离开临界区时,Mutex被自动解锁并销毁

1.6.2 Condition条件变量类

在这里插入图片描述

采用RAII技法封装了一个与特定mutex_相绑定的Condition条件变量

1.6.3 CountDownLatch计数类

在这里插入图片描述

这是一个对条件变量的封装类,既可以用于所有子线程等待主线程发起 “起跑” ,也可以用于主线程等待子线程初始化完毕才开始工作。

1.7 阻塞队列

1.7.1 BlockingQueue无界缓冲区模板类

在这里插入图片描述

该类的对象是生产者-消费者模型的基础,可用于实现线程池的任务队列(muduo库的线程池并未采用该类,而是使用的std::queue??

1.7.2 BoundedBlockingQueue有界缓冲区模板类

在这里插入图片描述

该类的对象是生产者-消费者模型的基础,可用于实现线程池的任务队列(muduo库的线程池并未采用该类,而是使用的std::queue??

1.8 ThreadPool线程池类

在这里插入图片描述
muduo库的线程池本质上是一个生产者与消费者模型,该类主要包括生产者线程队列以及任务队列queue_两个成员

1.9 Singleton单例模板类

在这里插入图片描述
主要是对pthread_once原语的封装,本函数使用初值为PTHREAD_ONCE_INIT的once_control变量保证init_routine()函数在本进程执行序列中仅执行一次

int pthread_once(pthread_once_t *once_control, void (*init_routine) (void))

1.10 线程特定数据类

1.10.1 ThreadLocal线程本地存储模板类

在这里插入图片描述
muduo库对线程特定数据的封装类

1.10.2 ThreadLocalSingleton线程本地单例模板类

在这里插入图片描述
在这里插入图片描述

1.11 日志相关类

1.11.1 Logger日志类

在这里插入图片描述
muduo库在这个类中定义了几个宏,可以实现日志输出,既可以输出到标准输出,又可以输出到宏

1.11.2 Impl日志实现类

在这里插入图片描述
Impl类包含日志的实现以及格式化

1.11.3 FixedBuffer模板类图

在这里插入图片描述

1.11.4 LogStream日志输出流类

在这里插入图片描述

1.11.5 LogFile日志滚动类

在这里插入图片描述
日志滚动类LogFile,是线程安全的,支持多个线程对同一个文件写入,但效率可能不比单个线程高,因为I/O不一定可以并行。若要提高多线程写入到同一个日志文件的效率,可以采用异步日志

LogFile类嵌套了一个File类,File类不是线程安全的,写入是通过不加锁的方式进行的

1.11.6 FileUtil小文件日志类

在这里插入图片描述

2. net

2.1 Reactor关键结构

2.1.1 EventLoop事件循环类

在这里插入图片描述

  1. EventLoop事件循环类,是对事件循环的抽象。
  2. one loop per thread意思是说每个线程最多只能有一个EventLoop对象。
  3. EventLoop对象构造的时候,会检查当前线程是否已经创建了其他EventLoop对象,如果已创建,终止程序(LOG_FATAL)。
  4. EventLoop构造函数会记住本对象所属线程(threadId_)。
  5. 创建了EventLoop对象的线程称为IO线程,其功能是通过EventLoop::loop()运行事件循环(while循环)。
  6. 一个EventLoop可以有多个Channel和FileDescriptor。
  7. EventLoop::loop()主要有四个作用,按顺序处理定时器事件,IO多路复用检测,处理检测返回的活动通道事件,处理自己的额外消息事件。

2.1.2 Channel通道类

在这里插入图片描述

  1. muduo中通过Channel对fd进行封装,fd可以是file descriptor,可以是socket,还可以是timefd,signalfd。其实更合适的说法是对fd事件相关方法的封装,例如负责注册fd的可读或可写事件到EvenLoop,又如fd产生事件后要如何响应。
  2. 一个fd对应一个channel, 它们是聚合关系,Channel在析构函数中并不会close掉这个fd(如socket是通过Socket类的对象离开临界区时close的,即RAII技法)。
  3. 它有一个handleEvent方法,当该fd有事件产生时EvenLoop会调用handleEvent方法进行处理,在handleEvent内部根据可读或可写事件调用不同的回调函数(回调函数可事先注册)。
  4. Channel类一般都不是单独使用的,通常都是用作其他类的成员,比如EvenLoop、TcpConnection、Acceptor、Connector这几个类。例如EvenLoop通过一个vector<Channel*> 对注册到其内的众多fd的管理,毕竟有了Channel就有了fd及其对应的事件处理方法,所以你会看到上图中EvenLoop与Channel是一对多的关系。
  5. Channel对象读写、更新都在一个I/O线程,读写不需要加锁。

2.1.3 Poller轮询器类

在这里插入图片描述

  1. Poller是一个抽象类,Poller是对I/O复用的抽象,有两个派生类PollPoller和EPollPoller,一个EventLoop包含一个Poller对象。
  2. PollPoller和EPollPoller使用一个map来存放描述符fd和对应的Channel类型的指针,这样我们就可以通过fd很方便的得到Channel了。

2.2 定时器类

定时器类的使用例子见P244
在这里插入图片描述
在这里插入图片描述

2.2.1 TimerId类

这个类表示一个定时器,用于创建一个序号为x的定时器。

2.2.2 Timer类

该类是一个定时器的封装,其包含定时器的内部实现细节。

2.2.3 TimerId类

该类是定时器处理流程的封装,其对外接口只有addtime()和cancel()。定时器的使用要通过EventLoop,EventLoop把addtime()和cancel()封装成更好用的runAt(),runAfter(),runEvery()等函数。

2.3 事件循环线程类

2.3.1 EventLoopThread IO线程类

在这里插入图片描述

  1. 任何一个线程,只要创建并运行了EventLoop,都称之为IO线程, IO线程不一定是主线程。
  2. 此类的功能是创建一个线程,并在线程的回调函数中创造一个eventloop对象。
  3. EventLoopThread是线程池中分配的线程类,内部是使用std::thread,每个线程持有一个自己的EventLoop。

2.3.2 EventLoopThreadPool IO线程池类

在这里插入图片描述

  1. EventLoopThreadPool是事件循环线程池,管理所有客户端连接,每个线程都有唯一一个事件循环。可以调用setThreadNum设置线程的数目。
  2. EventLoopThreadPool类又叫IO线程池类,由1个主线程+N个子线程组成,“1+N”的模式一般用在服务端,主线程用于接收所有客户端的连接请求,各子线程用于处理多个客户端的IO事件。给线程池分配客户端的时候采取循环分配的方式,保证线程池的负载平衡。多个客户端的IO操作可以对应于1个IO线程,但是1个客户端的IO操作不能对应于多个IO线程,因为多个IO线程抢占执行同一个客户端的读写操作会因为可能导致串写而需要用互斥锁。
  3. EventLoopThread作为EventLoopThreadPool的成员对象,多个EventLoopThread对象保存到std::vector中构成了EventLoopThreadPool的线程池。

2.4 套接字封装类

2.4.1 Endian类

封装了字节序转换函数(全局函数,位于muduo::net::sockets名称空间中).

2.4.2SocketsOps类

封装了socket相关系统调用(全局函数,位于muduo::net::sockets名称空间中)。

2.4.3 Socket类

  1. 用RAII方法封装套接字文件描述符。
  2. Sokcet类内部只保存一个套接字文件描述符。
  3. 提供了常用的套接字相关的操作,例如:绑定套接字,监听,接受一个连接,关闭写操作,获取Tcp信息。
  4. 设置套接字为NO_DELAY模式,设置地址重用,端口重用,设置keepalive。

2.4.4 InetAddress类

网际地址sockaddr_in封装。

2.5 服务器端相关类

2.5.1 Acceptor被动接受连接类

  1. Acceptor用于接收client的连接请求,建立连接。
  2. Acceptor的主要功能包括socket、bind、listen,并调用注册的回调函数来处理新到的连接。
  3. Acceptor主要负责的就是对lfd的读事件的处理,当有读事件到来的时候,即会调用handleRead()这个函数来调用构造时传给他的回调函数。
  4. Acceptor的数据成员包括Socket、Channel,Acceptor的socket是listening socket(即serversocket)。Channel用于观察此socket的readable事件,并回调Accptor::handleRead(),后者调用accept(2)来接受新连接,并回调用户callback。
  5. Acceptor类在上层应用程序中我们不直接使用,而是把它封装作为TcpServer的成员,生命期由后者控制。

2.5.2 TcpServer服务器类

  1. TcpServer中封装了EventLoopThreadPool,因此TcpServer中的EventLoop对象为main Reactor,EventLoopThreadPool为sub Reactor。当新建连接到达后,TcpServer创建一个新的TcpConnection对象来保存这个连接,设置这个新连接的回调函数,之后在EventLoopThreadPool中取一个EventLoop对象来作为这个新连接的reactor。
  2. 在TcpServer中,建立连接描述符和TcpConnection之间的关系,通过stl中map来实现,map<int,TcpConnectionPtr>这样可以高效的查找,删除,插入操作。
  3. TcpServer本身有一个EventLoop,主要用于对接受连接事件的监听,EventLoopPool也为TcpServer的成员,使用轮转法为每一个新的TcpConnection选择EventLoop。
  4. TcpServer类用来统一管理Acceptor和TcpConnection,为TcpConnection和Acceptor这两个类注册回调函数。

2.6 TcpConnection Tcp连接类

  1. TcpConnection用来表示一次TCP 连接,需要通过服务器端的connfd或者客户端的sockfd建立,并且可以对connfd和sockfd进行读写操作。
  2. TcpConnection相当于是对服务器和客户端之间连接的一种抽象,服务器端所建立的TcpConnection和客户端所建立的TcpConnection并不是同一个。
  3. 类中主要是EventLoop、Socket以及Channel这三个类对象指针,其中第一个用于指向当前连接所属的事件循环,而Socket用来指明用于网络通信的cfd,Channel表示本次连接的通道,用来封装fd的各种行为,在TcpConnection这个类构造的时候,便会将对应的读写回调处理函数注册进Channel中。
  4. TcpConnection中包含有封装好的读写 buffer, 用来收发数据。
  5. TcpConnection的生命周期由智能指针shared_ptr来管理。

2.7 客户端相关类

2.7.1 Connector主动发起连接类

  1. Connector只负责建立socket连接,不负责创建TcpCOnnection(TcpClient类实现TcpCOnnection的创建)。
  2. 外部调用Connector::start就可以发起连接,Connector具有重连的功能和停止连接的功能,连接成功建立后返回到TcpClient。

2.7.2 TcpClient客户类

  1. muduo用TcpClient发起连接,TcpClient有一个Connector连接器,TCPClient使用Connector发起连接, 连接建立成功后, 用socket创建TcpConnection来管理连接, 每个TcpClientclass只管理一个TcpConnecction,连接建立成功后设置相应的回调函数。很显然,TcpClient用来管理客户端连接,真正连接交给Connector。
  2. TcpClient具备TcpConnection断开之后重新连接的功能,加上Connector具备反复尝试连接的功能,因此客户端和服务器的启动顺序无关紧要。可以先启动客户端,一旦服务器启动,半分钟之内即可恢复连接(由Connector::kMaxRetryDelayMs常数控制);再客户端运行期间服务器可以重启,客户端也会自动重连。
  3. 连接断开后初次重试的延迟时间是随机的,比方说服务器崩溃,它所有的客户端连接同时断开,然后0.5s之后同时再次发起连接,这样既可能造成SYN丢包,也可能给服务器带来短期大负载,影响其服务质量。因此每个TcpClient应该等待一段随机的时间(0.5~2s),再充实,避免拥塞。
  4. 发起连接的时候如果发生TCP SYN丢包,那么系统默认的重试间隔是3s,这期间不会返回错误码,而且这个间隔似乎不容易修改。如果需要缩短间隔,可以再用一个定时器,在0.5s或1s之后发起另一个链接。如果有需求的话,这个功能可以做到Connector中。

2.8 Buffer应用层缓冲区类

为什么采用non-blocking网络编程中应用层buffer是必需的?

  1. non-blockingIO的核心思想是避免阻塞在read()或write()或其他IO系统调用上,这样可以最大限度地复用thread-of-control。让一个线程能服务于多个socket连接。IO线程只能阻塞在IOmultiplexing函数上,如select/poll/epoll_wait。这样一来,应用层的缓冲是必需的,每个TCPsocket都要有stateful的input buffer和output buffer。
  2. Tcpconnection必须要有output buffer,考虑一个场景,程序想通过TCP连接发送100KB的数据,但是在write调用中,操作系统只接受了80KB,肯定不想在此等待,因为不知道会等多久(取决于对方什么时候接受数据,然后滑动TCP窗口)。程序需要尽快交出控制权,返回eventloop。在这种情况下,剩余的20KB怎么办?
  3. 对于应用程序而言,它只管生成数据,它不关心到底数据是一次性发送还是分多少次发送的,这些由网络库关心,程序只需要调用TcpConnection::send()就行了,网络库会负责到底。网络库应该接管剩余的20KB数据,把它保存在该TCPconnection的output buffer里,然后注册pollout事件。 当然如果写完了,那么应该停止关注POLLOUT事件,以免造成busy loop,因为muduo采用的是level trigger。
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值