术语概念描述:

IO有内存IO、网络IO和磁盘IO三种,通常我们说的IO指的是后两者。

阻塞和非阻塞,是函数/方法的实现方式,即在数据就绪之前是立刻返回还是等待。

以文件IO为例,一个IO读过程是文件数据从磁盘→内核缓冲区→用户内存的过程。同步与异步的区别主要在于数据从内核缓冲区→用户内存这个过程需不需要用户进程等待。有个数据拷贝的过程,是拷贝完再通知还是在内核缓冲区就通知。(网络IO把磁盘换做网卡即可)

Linux IO模型

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

同步阻塞

去餐馆吃饭,点一个自己最爱吃的盖浇饭,然后在原地等着一直到盖浇饭做好,自己端到餐桌就餐。这就是典型的同步阻塞。当厨师给你做饭的时候,你需要一直在那里等着。

网络编程中,读取客户端的数据需要调用recvfrom。在默认情况下,这个调用会一直阻塞直到数据接收完毕,就是一个同步阻塞的IO方式。这也是最简单的IO模型,在通常fd(文件描述句柄)较少、就绪很快的情况下使用是没有问题的。

同步非阻塞

你每次点完饭就在那里等着,突然有一天你发现自己真傻。于是,你点完之后,就回桌子那里坐着,然后估计差不多了,就问老板饭好了没,如果好了就去端,没好的话就等一会再去问,依次循环直到饭做好。这就是同步非阻塞。

这种方式在编程中对socket设置O_NONBLOCK即可。但此方式仅仅针对网络IO有效,对磁盘IO并没有作用。因为本地文件IO就没有被认为是阻塞,我们所说的网络IO的阻塞是因为网路IO有无限阻塞的可能,而本地文件除非是被锁住,否则是不可能无限阻塞的,因此只有锁这种情况下,O_NONBLOCK才会有作用。而且,磁盘IO时要么数据在内核缓冲区中直接可以返回,要么需要调用物理设备去读取,这时候进程的其他工作都需要等待。因此,后续的IO复用和信号驱动IO对文件IO也是没有意义的。

IO复用

你点一份饭然后循环的去问好没好显然有点得不偿失,还不如就等在那里直到准备好,但是当你点了好几样饭菜的时候,你每次都去问一下所有饭菜的状态(未做好/已做好)肯定比你每次阻塞在那里等着好多了。当然,你问的时候是需要阻塞的,一直到有准备好的饭菜或者你等的不耐烦(超时)。这就引出了IO复用,也叫多路IO就绪通知。这是一种进程预先告知内核的能力,让内核发现进程指定的一个或多个IO条件就绪了,就通知进程。使得一个进程能在一连串的事件上等待。

IO复用的实现方式目前主要有select、poll和epoll。

select和poll的原理基本相同:

  • 注册待侦听的fd(这里的fd创建时最好使用非阻塞)

  • 每次调用都去检查这些fd的状态,当有一个或者多个fd就绪的时候返回

  • 返回结果中包括已就绪和未就绪的fd

相比select,poll解决了单个进程能够打开的文件描述符数量有限制这个问题:select受限于FD_SIZE的限制,如果修改则需要修改这个宏重新编译内核;而poll通过一个pollfd数组向内核传递需要关注的事件,避开了文件描述符数量限制。此外,select和poll共同具有的一个很大的缺点就是包含大量fd的数组被整体复制于用户态和内核态地址空间之间,开销会随着fd数量增多而线性增大。

select和poll就类似于上面说的就餐方式。但当你每次都去询问时,老板会把所有你点的饭菜都轮询一遍再告诉你情况,当大量饭菜很长时间都不能准备好的情况下是很低效的。于是,老板有些不耐烦了,就让厨师每做好一个菜就记下来他。这样每次你再去问的时候,他会直接把已经准备好的菜告诉你,你再去端。这就是事件驱动IO就绪通知的方式epoll。

epoll的出现,解决了select、poll的缺点:

  • 基于事件驱动的方式,避免了每次都要把所有fd都扫描一遍。

  • epoll_wait只返回就绪的fd。

  • epoll使用nmap内存映射技术避免了内存复制的开销。

  • epoll的fd数量上限是操作系统的最大文件句柄数目,这个数目一般和内存有关,通常远大于1024。

此外,对于IO复用还有一个水平触发和边缘触发的概念:

  • 水平触发:当就绪的fd未被用户进程处理后,下一次查询依旧会返回,这是select和poll的触发方式。

  • 边缘触发:无论就绪的fd是否被处理,下一次不再返回。理论上性能更高,但是实现相当复杂,并且任何意外的丢失事件都会造成请求处理错误。epoll默认使用水平触发,通过相应选项可以使用边缘触发。

信号驱动

上文的就餐方式还是需要你每次都去问一下饭菜状况。于是,你再次不耐烦了,就跟老板说,哪个饭菜好了就通知我一声吧。然后就自己坐在桌子那里干自己的事情。更甚者,你可以把手机号留给老板,自己出门,等饭菜好了直接发条短信给你。这就类似信号驱动的IO模型。

流程如下:

  • 开启套接字信号驱动IO功能

  • 系统调用sigaction执行信号处理函数(非阻塞,立刻返回)

  • 数据就绪(在内核缓冲区),生成sigio信号,通过信号回调通知应用来读取数据。

异步非阻塞

之前的就餐方式,到最后总是需要你自己去把饭菜端到餐桌。这下你也不耐烦了,于是就告诉老板,能不能饭好了直接端到你的面前或者送到你的家里(数据在用户内存就绪)。这就是异步非阻塞IO了。

对比信号驱动IO,异步IO的主要区别在于:信号驱动由内核告诉我们何时可以开始一个IO操作(数据在内核缓冲区中),而异步IO则由内核通知IO操作何时已经完成(数据已经在用户空间中)。异步IO又叫做事件驱动IO,在Unix中,POSIX1003.1标准为异步方式访问文件定义了一套库函数,定义了AIO的一系列接口。使用aio_read或者aio_write发起异步IO操作。使用aio_error检查正在运行的IO操作的状态。

网络编程模型

Java的I/O发展简史:

从JDK1.0到JDK1.3,Java的I/O类库都非常原始,很多UNIX网络编程中的概念或者接口在I/O类库中都没有体现,例如Pipe、Channel、Buffer和Selector等。2002年发布JDK1.4时,NIO以JSR-51的身份正式随JDK发布。它新增了个java.nio包,提供了很多进行异步I/O开发的API和类库,主要的类和接口如下。

  1. 进行异步I/O操作的缓冲区ByteBuffer等;
  2. 进行异步I/O操作的管道Pipe;
  3. 进行各种I/O操作(异步或者同步)的Channel,包括ServerSocketChannel和SocketChannel;
  4. 多种字符集的编码能力和解码能力;
  5. 实现非阻塞I/O操作的多路复用器selector;
  6. 基于流行的Perl实现的正则表达式类库;
  7. 文件通道FileChannel。

新的NIO类库的提供,极大地促进了基于Java的异步非阻塞编程的发展和应用,但是,它依然有不完善的地方,特别是对文件系统的处理能力仍显不足,主要问题如下。

  1. 没有统一的文件属性(例如读写权限);
  2. API能力比较弱,例如目录的级联创建和递归遍历,往往需要自己实现;
  3. 底层存储系统的一些高级API无法使用;
  4. 所有的文件操作都是同步阻塞调用,不支持异步文件读写操作。

2011年7月28日,JDK1.7正式发布。它的一个比较大的亮点就是将原来的NIO类库进行了升级,被称为NIO2.0。

NIO2.0由JSR-203演进而来,它主要提供了如下三个方面的改进。

  1. 提供能够批量获取文件属性的API,这些API具有平台无关性,不与特性的文件系统相耦合,另外它还提供了标准文件系统的SPI,供各个服务提供商扩展实现;
  2. 提供AIO功能,支持基于文件的异步I/O操作和针对网络套接字的异步操作;
  3. 完成JSR-51定义的通道功能,包括对配置和多播数据报的支持等。

上文讲述了UNIX环境的五种IO模型。基于这五种模型,在Java中,随着NIO和NIO2.0(AIO)的引入,一般具有以下几种网络编程模型:

  1. BIO
  2. NIO
  3. AIO

BIO

BIO是一个典型的网络编程模型,是通常我们实现一个服务端程序的过程,步骤如下:

  • 主线程accept请求阻塞

  • 请求到达,创建新的线程来处理这个套接字,完成对客户端的响应。

  • 主线程继续accept下一个请求

这种模型有一个很大的问题是:当客户端连接增多时,服务端创建的线程也会暴涨,系统性能会急剧下降。因此,在此模型的基础上,类似于 tomcat的bio connector,采用的是线程池来避免对于每一个客户端都创建一个线程。有些地方把这种方式叫做伪异步IO(把请求抛到线程池中异步等待处理)。

NIO

JDK1.4开始引入了NIO类库,这里的NIO指的是Non-blcok IO,主要是使用Selector多路复用器来实现。Selector在Linux等主流操作系统上是通过epoll实现的。

NIO的实现流程,类似于select:

  • 创建ServerSocketChannel监听客户端连接并绑定监听端口,设置为非阻塞模式。

  • 创建Reactor线程,创建多路复用器(Selector)并启动线程。

  • 将ServerSocketChannel注册到Reactor线程的Selector上。监听accept事件。

  • Selector在线程run方法中无线循环轮询准备就绪的Key。

  • Selector监听到新的客户端接入,处理新的请求,完成tcp三次握手,建立物理连接。

  • 将新的客户端连接注册到Selector上,监听读操作。读取客户端发送的网络消息。

  • 客户端发送的数据就绪则读取客户端请求,进行处理。

相比BIO,NIO的编程非常复杂。

AIO

JDK1.7引入NIO2.0,提供了异步文件通道和异步套接字通道的实现,是真正的异步非阻塞IO, 对应于Unix中的异步IO。

  • 创建AsynchronousServerSocketChannel,绑定监听端口

  • 调用AsynchronousServerSocketChannel的accpet方法,传入自己实现的CompletionHandler。包括上一步,都是非阻塞的

  • 连接传入,回调CompletionHandler的completed方法,在里面,调用AsynchronousSocketChannel的read方法,传入负责处理数据的CompletionHandler。

  • 数据就绪,触发负责处理数据的CompletionHandler的completed方法。继续做下一步处理即可。

  • 写入操作类似,也需要传入CompletionHandler。

总结

Linux IO模型和网络编程模型的更多相关文章

  1. Java网络编程和NIO详解3:IO模型与Java网络编程模型

    Java网络编程和NIO详解3:IO模型与Java网络编程模型 基本概念说明 用户空间与内核空间 现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32 ...

  2. linux网络编程模型

    1.编程模型 Linux网络编程模型是基于socket的编程模型

  3. IOCP模型与网络编程

    IOCP模型与网络编程 一.前言:        在老师分配任务(“尝试利用IOCP模型写出服务端和客户端的代码”)给我时,脑子一片空白,并不知道什么是IOCP模型,会不会是像软件设计模式里面的工厂模 ...

  4. 网络编程模型(C/S模型和B/S模型)

    目录 网络应用编程模型 互联网与企业内部网 早期计算机网络的通信模型 C/S模式 B/S模式 B/S 和 C/S 的区别 网络应用编程模型 互联网与企业内部网 网络的两个含义: 互联网 :互联网(In ...

  5. Linux下高并发网络编程

      Linux下高并发网络编程 1.修改用户进程可打开文件数限制 在Linux平台上,无论编写客户端程序还是服务端程序,在进行高并发TCP连接处理时, 最高的并发数量都要受到系统对用户单一进程同时可打 ...

  6. IO模式设置网络编程常见问题总结—IO模式设置,阻塞与非阻塞的比较,recv参数对性能的影响—O_NONBLOCK(open使用)、IPC_NOWAIT(msgrcv)、MSG_DONTWAIT(re

    非阻塞IO 和阻塞IO: 在网络编程中对于一个网络句柄会遇到阻塞IO 和非阻塞IO 的概念, 这里对于这两种socket 先做一下说明:       基本概念: 阻塞IO:: socket 的阻塞模式 ...

  7. Spark编程模型(RDD编程模型)

    Spark编程模型(RDD编程模型) 下图给出了rdd 编程模型,并将下例中用 到的四个算子映射到四种算子类型.spark 程序工作在两个空间中:spark rdd空间和 scala原生数据空间.在原 ...

  8. [国嵌攻略][090][linux网络编程模型]

    编程模型 Socket的实质就是一个接口,利用该接口,用户在使用不同的网络协议时,操作函数得以统一.而针对不同协议的差异性操作,则交给了Socket去自行解决. TCP编程模型 UDP编程模型

  9. 【网络编程】Socket套接字网络编程模型

    一.Linux网络模型 -- Socket套接字编程 图片:Socket 抽象层 Socket编程--不同协议,统一接口 Socket的实质就是一个接口, 利用该接口,用户在使用不同的网络协议时,操作 ...

随机推荐

  1. 3.kvm的基本管理

    1.查看KVM虚拟机配置文件 #KVM虚拟机默认配置文件位置 [root@kvm qemu]# pwd /etc/libvirt/qemu [root@kvm qemu]# ll total 12 # ...

  2. 【STL】 set集合容器常用用法

    set集合容器:实现了红黑树的平衡二叉检索树的数据结构,插入元素时,它会自动调整二叉树的排列,把元素放到适当的位置,以保证每个子树根节点键值大于左子树所有节点的键值,小于右子树所有节点的键值:另外,还 ...

  3. python if __name__ == '__main__'解析

    废话不多说,正题: python中所有的模块都有一个内置属性 __name__,一个模块的 __name__ 的值取决于如何应用模块.如果 import 一个模块,那么模块__name__ 的值通常为 ...

  4. Vim 强大的配置

    新建文件.vimrc,然后复制如下内容,并将该文件放到vim安装目录下 map <F9> :call SaveInputData()<CR> func! SaveInputDa ...

  5. ibatis中使用like模糊查询

    select * from table1 where name like '%#name#%' 两种有效的方法: 1) 使用$代替#.此种方法就是去掉了类型检查,使用字符串连接,不过可能会有sql注入 ...

  6. Android笔记:ListView

    listview属性 android:divider属性,可以指定ListView 分隔线的颜色,#0000 表示将分隔线设为透明色. listview效率的问题 adapter的三个参数int po ...

  7. 建立controller

    复制controller,重建controller 改: @Controller("[productController]") @RequestMapping("/[pr ...

  8. iOS 控制屏幕旋转

    在你想支持横竖屏的viewController里面重写两个方法: 1 2 3 4 5 6 7 8 9 10 11 // 支持设备自动旋转 - (BOOL)shouldAutorotate {      ...

  9. fork与vfork的区别与联系

    fork()与vfock()都是创建一个进程,那他们有什么区别呢?总结有以下三点区别: 1. fork ():子进程拷贝父进程的数据段,代码段 vfork ( ):子进程与父进程共享数据段 2. fo ...

  10. 安装CocoaPods报错 - [!] The dependency `AFNetworking (~> 3.1.0)` is not used in any concrete target.

    今天新机装cocopods时,等安装完毕发觉出现[!] The dependency `AFNetworking (~> 3.1.0)` is not used in any concrete ...