muduo学习笔记
概念
阻塞和非阻塞
ssize_t recv(int sockfd,void *buf,size_t : len,int : flag);
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程
同步和异步
同步:
需要自己将数据放到缓冲区
char buf[1024]={0};
int size = recv(sockfd, buf, 1024, 0);
if( size>0 ){
...
}
异步:
应用程序自己往下执行,由操作系统将数据放入缓冲区并发出通知。
预先设置通知方式,操作系统提供异步IO接口。 Linux下(aio_read,aio_write)
同步和异步的区别:请求发出后,是否需要等待结果,才能继续执行其他操作。
总的来说,同步和异步关注的是任务完成消息通知的机制,而阻塞和非阻塞关注的是等待任务完成时请求者的状态。
Unix/Linux上的5种IO模型
典型的一次IO两个阶段:数据就绪和数据读写
阻塞 Blocking
进程阻塞于read
非阻塞 non-blocking
进程调用read,判断EAGAIN反复调用
IO复用 IO multiplexing
进程阻塞于select/poll/epoll等待套接字变为可读,阻塞于read读数据
信号驱动 signal-driven
进程继续执行,阻塞于read读数据。于非阻塞IO的区别在于提供了消息通知机制,无需用于进程轮询查询
异步 asynchronous
典型异步非阻塞状态,Node.js采用的IO模型
网络服务器设计
Reactor模型
reactor主要存储了事件和事件对应的处理器
epoll
select和poll的缺点
select的缺点:
- 能监视的最大文件描述符存在限制(1024),而且由于是轮询的方式,一旦监视的文件描述符增多,性能越差
- 内核于用户空间的内存拷贝问题,产生大量开销
select
返回整个句柄数据,应用程序需要便利整个数组才能知道那个句柄发生事件- 水平触发模式,如果没有完成一个已经就绪的文件描述符进行IO,那么下次
select
还是会通知这个描述
和select
相比,poll
采用的是链表存储文件描述符,除了第一点其余三个缺点均存在。
epoll的原理和优势
与poll/select机制完全不同,
epoll
在Linux
内核申请一个简易的文件系统,IO效率高
主要分成3个流程
- 调用
epoll_create()
建立一个epoll
对象 - 调用
epoll_ctl
向epoll
对象中 - 调用
epoll_wait
收集发生事件的sockfd
触发模式
LT模式
epoll默认的模式
数据没有读完就会一直上报
ET模式
数据只会上报一次
muduo
采用的是LT模式
大概原因:
-
不丢失数据
- 没有读完,内核会不断上报
-
低延时处理
- 每次读数据只需要一次系统调用,照顾了连接的公平性
-
跨平台
- 与
select
一样可以跨平台使用
- 与
关键组件
noncpoyable
很多类都继承了这个类,是为了让类无法拷贝构造和赋值
class noncopyable{
public:
noncopyable(const noncopyable& )=delete;
noncopyable& operator=(const noncopyable&)=delete;
protected:
noncopyable()=default;
~noncopyable()=default;
}
Channel
封装了
fd
、events
、revents
以及一组回调
fd
:往poller
上注册的文件描述符events
:事先关注的事件revent
:文件描述符所返回的事件,根据相应的事件触发相应的回调。
分为两种channel
,一个是用于listenFd
用于接收连接,一种是connFd
是已建立连接的客户端,其中listenFd
封装为acceptorChannel
,connFd
封装为connectionChannel
;
Poller和EPollPoller - Demultiplex
poller
中记录了一个表unorder_map<int,channel*>
,如果有事件发生,就找到对应的channel
,其中就记录了详细的事件回调
EventLoop - Reactor
保存了一系列的
Channel
,即ChannelList
,存储了活跃的Channel
还有非常重要的weakFd
,std::unique_ptr<Channel> wakeUpChannel
,一个weakupFd
隶属于一个loop
,二者一一对应,驱动loop
是通过往weakupFd
中写入数据,weakupfd
也封装成了Channel
注册在了EPollPoller
上
Thread和EventLoopThread
EventLoopThreadPool
getNextLoop()
:通过轮询算法获取下一个subloop
一个Thread
对应一个loop
=> one loop peer thread
Acceptor
主要封装了
listenFd
相关的操作,包括socket
bind
listen
,然后扔给baseloop
Buffer
缓冲区,应用写数据->写入缓冲区->Tcp发送缓冲区->
send
+------------------+-------------------+---------------+
| preable bytes | readable bytes | writable |
+------------------+-------------------+---------------+
| | | |
0 readIndex writeIndex size
TcpConnection
一个成功连接的客户端对应一个
TcpConnection
封装了socket
channel
callback
,发送、接收缓冲区
TcpServer
封装了
Acceptor
EventLoopThreadPool
和一堆callback
connectionMap connection_
,记录所有的连接
初始化流程
- 构建
TcpServer
对象,在TcpServer
构造函数中:Acceptor->setNewConnectionCallback
,设置的就是Tcp::newConnection
,有新用户连接时,响应的就是这个函数 - 通过轮询算法选择
subLoop
,newConnection
创建TcpConnection
对象并且注册回调,closeCallback->TcpServer::removeConnection
,
ioloop->runInLoop->TcpConnection::connectionEstablished
- 调用
start()
函数用于启动,首先调用threadPool.start(startCallback)
,最终就是创建子线程并开启loop.loop()
; - 然后调用
_loop_->runInLoop(std::bind(&Acceptor::listen, _acceptor_.get()));
,就是将Acceptor
注册 - 开启
baseloop.loop()
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 在鹅厂做java开发是什么体验
· 百万级群聊的设计实践
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战
· 永远不要相信用户的输入:从 SQL 注入攻防看输入验证的重要性
· 全网最简单!3分钟用满血DeepSeek R1开发一款AI智能客服,零代码轻松接入微信、公众号、小程