概念
阻塞和非阻塞
网络 I O 阶段一: 数据准备 { 阻塞 : 调用 I O 方法线程进入阻塞状态 非阻塞 : 不会改变线程状态 , 通过返回值判断 ( 需要将 s o c k f d 设置为非阻塞状态 ) \bf{ 网络 \rm{IO} 阶段一:}\\ 数据准备 \begin{cases} 阻塞:\quad调用 \rm{IO} 方法线程进入阻塞状态 \quad \\ \\非阻塞:\quad不会改变线程状态,通过返回值判断(需要将sockfd设置为非阻塞状态)\end{cases} 网络IO阶段一:数据准备⎩ ⎨ ⎧阻塞:调用IO方法线程进入阻塞状态非阻塞:不会改变线程状态,通过返回值判断(需要将sockfd设置为非阻塞状态)
ssize_t recv(int sockfd,void *buf,size_t : len,int : flag);
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态.
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程
同步和异步
网络 I O 阶段二: 数据读写 { 同步 : ( 订票 → 等待出票 → 拿票 ) 异步 : ( 订票 → 干自己的事 → 出票 ( 机场通知拿票 ) ) \bf{网络\rm{IO}阶段二:}\\ 数据读写 \begin{cases} 同步:(订票→等待出票→拿票) \\ \\ 异步:(订票→干自己的事→出票(机场通知拿票))\end{cases} 网络IO阶段二:数据读写⎩ ⎨ ⎧同步:(订票→等待出票→拿票)异步:(订票→干自己的事→出票(机场通知拿票))
同步:
需要自己将数据放到缓冲区
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相关的操作,包括socketbindlisten,然后扔给baseloop
Buffer
缓冲区,应用写数据->写入缓冲区->Tcp发送缓冲区->
send
+------------------+-------------------+---------------+
| preable bytes | readable bytes | writable |
+------------------+-------------------+---------------+
| | | |
0 readIndex writeIndex size
TcpConnection
一个成功连接的客户端对应一个
TcpConnection
封装了socketchannelcallback,发送、接收缓冲区
TcpServer
封装了
AcceptorEventLoopThreadPool和一堆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()
文章详细介绍了Unix/Linux上的五种IO模型,包括阻塞、非阻塞、IO复用、信号驱动和异步模型。同时,讨论了同步和异步在网络IO阶段的区别,以及Reactor模型在网络服务器设计中的应用。epoll作为高效的IO复用技术,其LT模式被muduo库采用。文章还提到了关键组件如Channel、Poller和EventLoop在处理网络连接和数据传输中的作用。
623

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



