项目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:
- 文件描述符受限1024,轮询方式扫描文件描述符,文件描述符数量越多性能越差
- 内核/用户空间内存拷贝问题,select监听的时候需要将被监听的fd 以及事件的句柄数据结构拷贝到内核,监听结束内核又需要返回给用户,拷贝开销大
- 返回的是整个句柄的数组,还需要遍历才能发现事件
- 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执行
本文探讨了Linux下的IO模型,如同步阻塞、同步非阻塞(io多路复用)、信号驱动、异步(Reactor模型)等,重点介绍了muduo框架中的EventLoop、EpollPoller、Thread和TcpServer组件,以及它们在处理网络IO时的角色和协作方式。
3711

被折叠的 条评论
为什么被折叠?



