IO模型解析

来源:互联网 发布:a标签执行js后跳转 编辑:程序博客网 时间:2024/05/21 03:54

IO模型解析

标签 : IO


1.1 IO模型概述

在服务器端网络编程我们经常会用到一些IO模型,有如下几种IO模型

  1. 同步阻塞IO
  2. 同步非阻塞IO
  3. IO多路复用
  4. 异步IO
  5. 信号驱动IO

1.2 IO模型涉及的基本概念。

要想好好理解上面的IO模型,我们首先来明确如下几个概念。
1. 同步:当程序发生一次功能调用的时候,需要等待这次调用返回然后才能进行下一步操作。也就是事情要一件一件做。
eg:单线程程序中的顺序执行。
2. 异步:当程序发生一次调用的时候,无需等待这次调用的返回,就可以继续执行程序。这次调用完成后会通过状态、通知、回调来通知调用者。
eg:ajax调用服务器。

同步异步的主要区别就是:数据拷贝的时候“进程”是否阻塞。

  1. 阻塞:当调用返回之前,程序的处在挂起的状态(CPU不会给分配时间片),调用返回后才会从挂起状态转换成运行状态。
  2. 非阻塞:发生调用后,调用函数不会阻塞这个线程。而是立刻返回。

阻塞和非阻塞的主要区别就是:应用程序的调用是否能立即返回。

在明确这些概念之后我们开始

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

0 0