阻塞IO、非阻塞IO、以及多路复用原理


提示:写完文章后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

  • 阻塞IO、非阻塞IO、以及多路复用原理
  • 什么是I/O
  • 一、BIO(阻塞IO)
  • 二、NIO
  • Select、Poll、Epoll
    • Select
    • poll
    • epoll
  • 总结

什么是I/O

在计算机系统中I/O就是输入(Input)和输出(Output)的意思,针对不同的操作对象,可以划分为磁盘I/O模型,网络I/O模型,内存映射I/O, Direct I/O、数据库I/O等,只要具有输入输出类型的交互系统都可以认为是I/O系统,也可以说I/O是整个操作系统数据交换与人机交互的通道,这个概念与选用的开发语言没有关系,是一个通用的概念。
在如今的系统中I/O却拥有很重要的位置,现在系统都有可能处理大量文件,大量数据库操作,而这些操作都依赖于系统的I/O性能,也就造成了现在系统的瓶颈往往都是由于I/O性能造成的。因此,为了解决磁盘I/O性能慢的问题,系统架构中添加了缓存来提高响应速度;或者有些高端服务器从硬件级入手,使用了固态硬盘(SSD)来替换传统机械硬盘;因此,一个系统的优化空间,往往都在低效率的I/O环节上,很少看到一个系统CPU、内存的性能是其整个系统的瓶颈。也正因为如此,Java在I/O上也一直在做持续的优化,从JDK 1.4开始便引入了NIO模型,大大的提高了以往BIO模型下的操作效率。
在介绍阻塞IO、非阻塞IO、以及多路复用的原理之前,先来看看一个网络IO的总体调用过程
1、客户端的进程想要向服务端的进程发送或申请数据
2、数据到达服务器端所在计算机的网卡,然后操作系统将其拷贝到内核所对应的socket缓冲区。
3、服务端进程将内核缓冲区的数据拷贝到自己的进程中,然后对数据进行处理(可能为读,可能为写)
4、服务端对处理完的数据发送到内核所对应的socket缓冲区,然后再从网卡发送到客户端。

一、BIO(阻塞IO)

BIO (Blocking I/O):同步阻塞I/O模式,数据的读取写入必须阻塞在一个线程内等待其完成。简单来试想一下一个服务端与客户端连接的一个场景。当有客户端连接时,服务器端需为其单独分配一个线程,如果该连接不做任何操作就会造成不必要的线程开销。BIO的缺点很明显,首先单独为每一个连接分配一个线程,线程的开销是很明显的,假如说有上万个连接,那么我就得分配上万个进程。对此的改进可能有一些人会想到用线程池,但是线程池要设多大,多大才合理,这个很难判断,但这也是一个优化的方法。
BIO总体流程:
1、服务器端启动一个SeverSocket
2、客户端启动Socket对服务器端发起通信,默认情况下服务器端需为每个客户端创建一个线程与之通讯
3、客户端发起请求后,先咨询服务器端是否有线程响应,如果没有则会等待或被拒绝
4、如果有线程响应,客户端线程会等待请求结束后,再继续执行

BIO的执行代码如下:

//BIO-服务器端
public class BIOSever {public static void main(String[] args) throws IOException {//在BIO中,可以使用线程池进行优化ExecutorService cachedThreadPool = Executors.newCachedThreadPool();ServerSocket serverSocket = new ServerSocket(6666);System.out.println("服务器已启动");while (true){System.out.println("等待客户端连接.....(阻塞中)");Socket socket = serverSocket.accept();System.out.println("客户端连接");cachedThreadPool.execute(new Runnable() {public void run() {handler(socket);}});}}//从客服端socket读取数据public static void handler(Socket socket){try{InputStream inputStream = socket.getInputStream();byte[] b = new byte[1024];while (true){System.out.println("等待客户端输入.....(阻塞中)");int read = inputStream.read(b);if (read != -1){System.out.println(new String(b, 0, read));}else {break;}}inputStream.close();}catch (Exception e){e.printStackTrace();}finally {try {socket.close();} catch (IOException e) {e.printStackTrace();}}}
}
//BIO-客户端
public class BIOClient {public static void main(String[] args) throws IOException {Socket socket = new Socket("localhost", 6666);OutputStream outputStream = socket.getOutputStream();Scanner scanner = new Scanner(System.in);while (scanner.hasNextLine()){String message = scanner.nextLine();if ("exit".equals(message)) {break;}outputStream.write(message.getBytes());}outputStream.close();socket.close();}
}

可以看到,当服务端分配一个线程处理客户端的时候,其服务端所分配的线程会调用read()函数,此函数其实会一直阻塞,等待数据的返回,而且该函数其实涉及到系统调用,即会从当前进程的用户态陷入到内核态,这也是一个很大的开销

二、NIO

BIO的缺点显而易见,首先想一下为什么会出现BIO,也就是说为什么线程要阻塞,其根本原因是内核要阻塞,那内核程序能不能支持一下非阻塞的方式,也即我管理的连接,当没有数据到达的时候,我返回-1,让其不阻塞。因此NIO就出现了,源自内核的支持,NIO在原则上可以让一个线程管理上万个连接。来看看NIO是如何做的
1、客户端想要连接服务端,服务端监听到客户端的连接,然后将这个连接分配到一个用于管理这些连接的线程上(可以理解为这个线程管理了一个具有多个连接的数组)
2、线程开始遍历当前数据,通过read()操作轮询是否有可读事件发生,如果有可读的事件发送,则进行读处理,如果没有,那么内核直接返回一个没有读事件发生的标识给线程,那么线程就可以一次遍历所有的连接来处理读写事件。

先来说一下该非阻塞IO的优点
1、不用像BIO一样创建多个线程,用一个线程就可以管理多个连接,减少了创建线程所带来的开销
看似我们解决了阻塞IO带来的问题,但是该阻塞IO也有一些缺点
1、虽然能用一个线程管理了多个连接,但是,不管连接中有没有可读可写的数据,都要进行遍历一边,如果连接数一多,那么这个O(n)的遍历复杂度还是挺消耗性能的
2、注意到,每次进行连接的读写的时候,都要调用read()或write的操作,上面说到,每一次read()和write()操作都要涉及到系统调用,从用户态陷入到内核态,想一想,N个连接就是N次的系统调用,这个开销还是挺大的

Select、Poll、Epoll

上述的NIO中,都是在用户态来完成轮询read或write操作,N个连接就是N次的系统调用,那有没有一种方式,即把我需要轮询的连接read和write操作转移到内核中?即我通过调用一个函数,然后这个函数就在内核中帮我监听这些连接,然后返回可读写的事件返回给用户态。而这其实就是内核所提供的select、poll、epoll函数

Select

Select函数如下

/* According to POSIX.1-2001 */
#include <sys/select.h>int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);void FD_CLR(int fd, fd_set *set);       //从fdset中删除fd
int  FD_ISSET(int fd, fd_set *set);     //判断fd是否已存在fdset
void FD_SET(int fd, fd_set *set);       //将fd添加到fdset
void FD_ZERO(fd_set *set);              //fdset所有位清0

select参数说明如下:

nfds:监控的文件描述符集中,待测试的最大描述符+1,这里可理解为用户空间需要监听的连接

readfds:监控有读数据到达文件描述符集合

writefds:监控有写数据到达文件描述符集合

exceptfds:监控异常发生达文件描述符集合

timeout:定时阻塞监控时间,3种情况:

1)NULL,永远等下去

2)设置 timeval ,等待固定时间

3)设置 timeval 里时间均为0,检查描述字后立即返回,轮询

这里不对源码做深入讲解,只需要知道内核提供了一个函数,帮助用户空间监听其连接,并返回有事件的连接,那么在用户空间,我们只需要轮询从select函数中返回的数据是否有可读写的标志,如果有,那么就做进一步的处理。而对比于上面的NIO,select将用户态的遍历转移到了内核态,也即通过一次系统调用就能返回可读写的事件。

poll

poll函数其实跟select的原理差不多,不同的地方是,select是把fd(可理解为连接)采用数组的方式存放,而poll是以链表的方式,采用链表的方式不需要一片连续的内存空间,这对内核空间来说非常重要。

epoll

epoll 是Linux内核中的一种可扩展IO事件处理机制,最早在 Linux 2.5.44内核中引入,可被用于代替POSIX select 和 poll 系统调用,并且在具有大量应用程序请求时能够获得较好的性能( 此时被监视的文件描述符数目非常大,与旧的 select 和 poll 系统调用完成操作所需 O(n) 不同, epoll能在O(1)时间内完成操作,所以性能相当高)

int epoll_create(int size);

创建一个epoll的句柄,size用来告诉内核需要监听的数目一共有多大。当创建好epoll句柄后,它就是会占用一个fd值,在linux下如果查看/proc/进程id/fd/,是能够看到这个fd的,所以在使用完epoll后,必须调用close() 关闭,否则可能导致fd被耗尽。

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

epoll的事件注册函数,第一个参数是 epoll_create() 的返回值,第二个参数表示动作,使用如下三个宏来表示:

EPOLL_CTL_ADD    //注册新的fd到epfd中;
EPOLL_CTL_MOD    //修改已经注册的fd的监听事件;
EPOLL_CTL_DEL    //从epfd中删除一个fd;

第三个参数是需要监听的fd,第四个参数是告诉内核需要监听什么事,struct epoll_event 结构如下

typedef union epoll_data
{void        *ptr;int          fd;__uint32_t   u32;__uint64_t   u64;
} epoll_data_t;struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
EPOLLIN     //表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT    //表示对应的文件描述符可以写;
EPOLLPRI    //表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR    //表示对应的文件描述符发生错误;
EPOLLHUP    //表示对应的文件描述符被挂断;
EPOLLET     //将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT//只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);

参数events用来从内核得到事件的集合,maxevents 告之内核这个events有多大,这个 maxevents 的值不能大于创建 epoll_create() 时的size,参数 timeout 是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。该函数返回需要处理的事件数目,如返回0表示已超时

总结

提示:这里对文章进行总结:

例如:以上就是今天要讲的内容,本文仅仅简单介绍了BIO、NIO、多路复用的原理

阻塞IO、非阻塞IO、以及多路复用原理相关推荐

  1. IO模型(阻塞,非阻塞,多路复用)

    在了解IO模型前,先了解什么叫IO,IO得操作是怎么样的? IO既输入输出,指的是一切操作程序或设备与计算机之间发生的数据传输的过程.它分为IO设备和IO接口两个部分. IO设备:就是指可以与计算机进 ...

  2. 什么是IO多路复用_IO多路复用同步异步阻塞和非阻塞

    转自:http://www.elecfans.com/baike/wangluo/fuyongqi/20180307644141.html 一.什么是socket? 我们都知道unix(like)世界 ...

  3. IO模型_阻塞_非阻塞_多路复用

    在了解IO模型前,先了解什么叫IO,IO得操作是怎么样的? IO既输入输出,指的是一切操作程序或设备与计算机之间发生的数据传输的过程.它分为IO设备和IO接口两个部分. IO设备:就是指可以与计算机进 ...

  4. 阻塞IO 非阻塞IO IO多路复用 异步IO 区别

    Linux中五种IO模型:阻塞IO模型.非阻塞IO模型.IO复用模型.信号驱动IO模型.异步IO模型 linux的内核将所有外部设备都看作一个文件来操作,针对一个文件的读写会调用内核提供的系统命令,返 ...

  5. python gevent模块 下载_Python协程阻塞IO非阻塞IO同步IO异步IO

    Python-协程-阻塞IO-非阻塞IO-同步IO-异步IO 一.协程 协程又称为微线程 CPU 是无法识别协程的,只能识别是线程,协程是由开发人员自己控制的.协程可以在单线程下实现并发的效果(实际计 ...

  6. 阻塞和非阻塞、同步和异步 、五种IO模型

    阻塞和非阻塞,同步和异步 1 例子 故事:老王烧开水. 出场人物:老张,水壶两把(普通水壶,简称水壶:会响的水壶,简称响水壶). 老王想了想,有好几种等待方式 1.老王用水壶煮水,并且站在那里,不管水 ...

  7. linux驱动系列学习之阻塞与非阻塞IO(六)

    一. 阻塞与非阻塞IO概念     阻塞操作是指在执行设备操作时,若不能获取资源,则挂起进程进入休眠状态,等待可满足条件后进行操作.被挂起的进程从调度器队列移动到挂起队列(睡眠状态).当操作驱动程序r ...

  8. python网络编程基础(线程与进程、并行与并发、同步与异步、阻塞与非阻塞、CPU密集型与IO密集型)...

    python网络编程基础(线程与进程.并行与并发.同步与异步.阻塞与非阻塞.CPU密集型与IO密集型) 目录 线程与进程并行与并发同步与异步阻塞与非阻塞CPU密集型与IO密集型 线程与进程 进程 前言 ...

  9. IO:同步,异步,阻塞,非阻塞

    IO - 同步,异步,阻塞,非阻塞 都是老生常谈的东西,多通读几遍,理解透彻! 实际上同步与异步是针对应用程序与内核的交互而言的.同步过程中进程触发IO操作并等待(也就是我们说的阻塞)或者轮询的去查看 ...

  10. Linux IO - 同步,异步,阻塞,非阻塞

    From:http://blog.csdn.net/historyasamirror/article/details/5778378 同步/异步,阻塞/非阻塞概念深度解析:http://blog.cs ...

最新文章

  1. sendmail邮件服务器支持账户名大小写
  2. 测试跟踪工具Bugzilla介绍
  3. 电脑故障扫描修复软件_253个电脑故障修复工具
  4. PPT 2010实现使用自定义主题付下载
  5. git的历史版本拉分支、回撤revert、回退reset
  6. 云服务器系统满了怎么办,云服务器磁盘空间满了怎么办
  7. javascript 字符串中单引号和双引号区别
  8. Mac 下 maven 安装与配置
  9. 如何有效管理远程开发团队
  10. greenplum小版本升级
  11. TensorFlow2.0 学习笔记(五):循环神经网络(RNN)
  12. React18 tearing tree issue 撕裂状态问题
  13. python3发起一个http请求
  14. 你写论文时发现了哪些非常神的网站?
  15. python随机森林变量重要性_推荐 :一文读懂随机森林的解释和实现(附python代码)...
  16. MySQL循环语句实战
  17. RabbitMQ初步到精通-第四章-RabbitMQ工作模式-Routing
  18. 01、低噪声放大电路设计——ATF-54143
  19. 修改Realtek瑞昱网卡硬件MAC地址突破路由器上网Mac地址绑定
  20. jQuery练习 下拉更换背景+百度音乐盒

热门文章

  1. 【银河麒麟V10】【服务器】系统负载分析
  2. mysql ddl复制_MySQL DDL-对库和表的操作
  3. css3+js金属光泽按钮
  4. JavaMail发邮箱(多人发送,抄送多人,多附件发送)
  5. 关于CorelDraw X8第一次安装时存在另一版本,而无法安装当前版本的问题
  6. 计算机网络原理谢仁希版第四章网络层总结
  7. NDS9435快速开关MOS管的使用
  8. Error: Flash Download failed - Cortex-M4
  9. 【树的四种遍历方法(遍历排序二叉树)】
  10. 福大软工1816 · 团队现场编程实战(抽奖系统)之 拖鞋旅游队