c++11重写moduo库

本文探讨了Linux下的IO模型,如同步阻塞、同步非阻塞(io多路复用)、信号驱动、异步(Reactor模型)等,重点介绍了muduo框架中的EventLoop、EpollPoller、Thread和TcpServer组件,以及它们在处理网络IO时的角色和协作方式。

项目gitee地址:https://gitee.com/cai-jinxiang/cjxmuduo

阻塞,非阻塞,同步,异步

  • 一次io的两个阶段:数据准备和数据读写

ssize_t recv(int sockfd, void* buf, size_t len, int flags); 非阻塞,当缓冲区无数据,程序直接返回
size =-1&& errno == EAGAIN || errno == EWOULDBLOCK //没有网络事件发生
size == 0 //对端关闭连接
size >0 读到数据

数据准备阶段:

  • 阻塞:调用IO方法的线程进入阻塞状态
  • 非阻塞:不会改变线程状态,通过返回值判断

数据读写阶段(IO的同步和异步,区别于并发(业务)的同步和异步):

  • 同步:

    • 调用recv(int sockfd, void* buf, size_t len, int flags);
    • 给提供一个buf,将fd读缓冲区的数据,copy到buf中(用户自己搬)
    • copy完,recv才会结束
  • 异步(在处理 IO 的时候,阻塞和非阻塞都是同步 IO。只有使用了特殊的 API 才是异步
    IO,aio_read,aio_write

    • 用户对sockfd的数据感兴趣
    • 用户定义一个buf,将buf交给操作系统,并注册一个sigio
    • 操作系统发现sockfd的可读,将数据copy到buf,copy完成后通知用户

业务层同步:A操作等待B操作结束,得到返回值
业务层异步:A操作告诉B操作,他感兴趣的事件及通知方式,A操作继续向下执行
B监听到相应事件发生后,B通知A,A开始相应处理

linux/unix下五种IO模型

同步阻塞
在这里插入图片描述

同步非阻塞
在这里插入图片描述
**io多路复用,**这也是同步io,通常使用非阻塞sockfd

  • 没有人真的会用轮询 (busy-pooling) 来检查某个 non-blocking IO 操作是否完成,这样太浪费CPU资源了。
  • IO-multiplex 一般不能和 blocking IO 用在一起,因为 blocking IO 中read()/write()/accept()/connect() 都有可能阻塞当前线程,这样线程就没办法处理其他 socket上的 IO 事件了。

在这里插入图片描述
信号驱动
内核在第一个阶段是异步,在第二个阶段是同步;与非阻塞IO的区别在于它提供了消息通知机制,不需
要用户进程不断的轮询检查,减少了系统API的调用次数,提高了效率。

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

Reactor模型

Event事件、Reactor反应堆、Demultiplex事件分发器(epoll)、Evanthandler事件处理器

向Reactor注册监听的Event(事件),Handler(回调函数),Reactor把事件注册在Demultiplex上,有事件发生,Demultiplex就通知Reactor,Reactor回调对应函数

在这里插入图片描述
在muduo中实际的实现是下图的形式,Reactor是上图的Reactor+Demultiplex,客户端向服务器发出连接后由mainReactor
负责打包
在这里插入图片描述

epoll,select,poll总结

select:

  1. 文件描述符受限1024,轮询方式扫描文件描述符,文件描述符数量越多性能越差
  2. 内核/用户空间内存拷贝问题,select监听的时候需要将被监听的fd 以及事件的句柄数据结构拷贝到内核,监听结束内核又需要返回给用户,拷贝开销大
  3. 返回的是整个句柄的数组,还需要遍历才能发现事件
  4. select是水平触发,未读取完成会不断触发
    poll:
    采用链表保存文件描述符,没有1024限制,但是其他三个确定还是存在

muduo简介

reactors in threads one loop per thread
一个mian reactor 负责accept连接
将连接分发到某个sub reactor 采用轮询机制分发,该连接的所有操作都在那个sub reactor中完成
如果有耗时间多的cpu密集型任务,可以提交到线程池中专门处理好使的计算任务

  • 使用时,只需要向网络库提供用户的连接断开函数,用户的可读可写事件

cjxmuduo

在这里插入图片描述

对此项目的介绍有一篇非常详细的文章
https://zhuanlan.zhihu.com/p/495016351

Channel

  • 对于fd的封装,包括监听的fd,以及感兴趣的事件和真正发生的事件

Poller,EpollPoller

  • Poller是一个抽象层,muduo支持poll和epoll两种io多路复用
  • cjxmuduo只实现了EpollPoller,
  • 一个EpollPoller中包含一个ChannelList,监听多个Channel

EventLoop

  • 包含一个Poller和ChannelList
  • 每个线程对应一个loop,在多线程模式下,会有一个mainloop和多个subloop
  • mainloop接受到新的连接后,轮询的方式交给subloop
  • loop之间会有一个fd和channel用于唤醒对方
  • 在TcpServer中有mainloop,当有新连接时,会getNextLoop,获取一个subloop的指针,将新连接分发出去,此时需要唤醒subloop(subloop会阻塞在epollwait)然后执行subloop->queueInLoop(),会向subloop的wakeupfd发消息,然后被唤醒,执行传入的cb
    在这里插入图片描述

Thread

  • 对thread的封装
    重要函数
  • start()
    * 创建thread
    * 执行func_() (此函数由创建线程类的时候传入,由EventLoopThread提供)
    * func_()中创建一个EventLoop 并开启loop

EventLoopThread

  • 包含一个Thread,一个loop
    重要函数
  • threadfunc() 此函数交给Thread,具体内容看Thread::func_()
  • start loop()
    * Thread.start()
    * 执行func_
    * 等待loop创建完成,赋值给自己的成员变量

EventLoopThreadPool

作为管理所有EventLoopThread的池子,构造函数需要传入一个用户创建的loop作为baseloop
重要函数

  • start
    • 根据设置的线程数量创建EventLoopThread
  • getnextloop()
    • 轮询返回EventLoop

TcpServer

拥有一个EventLoopThreadPool,Acceptor的智能指针,一个baseloop,
重要函数

  • start()
    • 将Acceptor注册到baseloop上,监听可读事件
  • newConnection()
    • 有新连接后封装为TcpConnection,给subloop,
    • 此函数交给Accetor执行
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值