IO模型解析
标签 : IO
1.1 IO模型概述
在服务器端网络编程我们经常会用到一些IO模型,有如下几种IO模型
- 同步阻塞IO
- 同步非阻塞IO
- IO多路复用
- 异步IO
- 信号驱动IO
1.2 IO模型涉及的基本概念。
要想好好理解上面的IO模型,我们首先来明确如下几个概念。
1. 同步:当程序发生一次功能调用的时候,需要等待这次调用返回然后才能进行下一步操作。也就是事情要一件一件做。
eg:单线程程序中的顺序执行。
2. 异步:当程序发生一次调用的时候,无需等待这次调用的返回,就可以继续执行程序。这次调用完成后会通过状态、通知、回调来通知调用者。
eg:ajax调用服务器。
同步异步的主要区别就是:数据拷贝的时候“进程”是否阻塞。
- 阻塞:当调用返回之前,程序的处在挂起的状态(CPU不会给分配时间片),调用返回后才会从挂起状态转换成运行状态。
- 非阻塞:发生调用后,调用函数不会阻塞这个线程。而是立刻返回。
阻塞和非阻塞的主要区别就是:应用程序的调用是否能立即返回。
在明确这些概念之后我们开始
2.1 同步阻塞IO
应用进程在发起recvfrom调用的时候。会一直阻塞。等到调用完成(完成数据的准备和数据的拷贝)。
2.2 同步非阻塞IO
recvfrom轮询发起调用,但是每次调用立即返回。所以是非阻塞。recvfrom进程依旧要等待数据拷贝完成后才能继续执行,所以是同步。加起来就是同步非阻塞。但是像这种不断轮询也会消耗CPU的资源。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。
2.3 IO多路复用
IO多路复用模式是建立在内核提供的select、poll函数的基础上,提供一种可以在同一线程内处理多个I请求的效果。
观察图中的流程我们也会发现select函数同样会阻塞,看起赖好像比阻塞模式并没有什么优势。但是这两个函数可以阻塞多个IO操作。直到数据准备完成,用户线程开始发起请求去读取准备好的数据。
用户可以注册多个socket然后不断的调用select来获取被激活的socket。这样就完成了一个线程处理多个IO请求的目的。伪代码如下。
{
//socket添加到select监视中
select(socket);
while(true) {
//阻塞方法 获取激活的socket
sockets = select();
for(socket : sockets) {
if(can_read(socket)) {
//读取数据
read(socket, buffer);
//处理逻辑
process(buffer);
}
}
}
}
linux还提供了一个epoll系统调用,epoll是基于事件驱动方式,而不是顺序扫描,当有fd就绪时,立即回调函数rollback;
虽然在上面讲述的方式中可以在一个线程内同时处理多个IO请求,但是IO请求的过程还是阻塞在Select函数上的,平均时间甚至比同步阻塞IO的时间还要长。如果IO请求不阻塞在select上,用户可以注册自己的socket或者IO请求,然后继续做自己的事情(不阻塞)。等到数据准备完成再进行处理。这样就可以提高CPU利用率。
IO多路复用模型使用Reactor设计模式实现了这个机制。
- EventHandler 抽象类表示IO时间的处理器。get_handle可以获取文件引用,handle_event可以完成对文件的操作,继承自EventHandler的子类可以定制不同的处理方式。
- Reactor 用于管理EventHandler(注册,删除等)。handle_events实现事件循环,不断调用同步事件多路分离器(一般是内核)的多路分离函数select,只要某个文件被激活(可读/写等),select就返回(阻塞),handle_events就会调用与文件句柄关联的事件处理器的handle_event进行相关操作。
如上图所示, 用户线程会向Reactor注册IO时间,Reactor的handle_events事件会循环处理(不断的调用多路分离函数)此时用户线程可以继续执行其他操作。当Reactor发现有被激活的socket的时候会通知用户线程(或者调用回调函数)执行handle_event进行数据的读取处理等工作。
用户线程使用IO多路复用模型的伪代码描述为
// class UserEventHandler
void handle_event() {
if(can_read(socket)) {
read(socket, buffer);
process(buffer);
}
}
{
Reactor.register(new UserEventHandler(socket));
}
用户需要重写EventHandler的handle_event函数进行读取数据、处理数据的工作,用户线程只需要将自己的EventHandler注册到Reactor即可。
Reactor中handle_events事件循环的伪代码大致如下。
// class Reactor
handle_events() {
while(1) {
sockets = select();
for(socket in sockets) {
get_event_handler(socket).handle_event();
}
}
}
事件循环不断地调用select获取被激活的socket,然后根据获取socket对应的EventHandler,执行器handle_event函数即可。
2.4 异步IO
告知内核启动某个操作,并让内核在整个操作完成后(包括将数据从内核拷贝到用户自己的缓冲区)通知我们。
参考文章
http://www.cnblogs.com/fanzhidongyzby/p/4098546.html
http://blog.csdn.net/jay900323/article/details/18141217
http://www.2cto.com/os/201408/326734.html
http://blog.csdn.net/colzer/article/details/8169075