目录
1.1 同步、异步、阻塞、非阻塞
    同步 VS 异步
        同步
        异步
    阻塞 VS 非阻塞
        阻塞
        非阻塞
    举例
        1)同步阻塞
        2)同步非阻塞
        3)异步阻塞
        4)异步非阻塞
1.2 Linux IO模型
    IO执行的两个阶段
    Linux的5种IO模型
    五种IO模型比较
1.3 JDK IO发展(BIO--->NIO--->AIO)
    BIO代码示例(未使用Netty的阻塞网络编程)
    NIO代码示例(未使用Netty的非阻塞网络编程)
    AIO代码示例(略)
1.5 Netty与NIO
    1.5.1 Netty对3种I/O模式的支持
        问题1: 为什么Netty仅支持NIO了?
            为什么不建议使用BIO(阻塞IO)?
            为什么删除掉已经做好的AIO支持?
        问题2:为什么Netty有多种NIO实现?
    1.5.2 代码:Netty使用BIO
    1.5.3 代码:Netty使用NIO
参考资料

1.1 同步、异步、阻塞、非阻塞

同步 VS 异步

同步与异步与(被调用者)消息的通知机制有关。

同步

同步处理是指被调用方得到最终结果之后才返回给调用方。比如,调用readfrom系统调用时,必须等待IO操作完成才返回给调用方。

异步

异步处理是指没得到最终结果时被调用方先返回应答,计算完最终结果后再通知并返回给调用方。比如:调用aio_read系统调用时,不必等IO操作完成就直接返回,调用结果通过信号来通知调用者。

阻塞 VS 非阻塞

阻塞与非阻塞与(调用者)等待消息通知时的状态有关。

阻塞

阻塞调用是指调用结果返回之前,当前线程会被挂起,调用线程只有在得到结果之后才会返回。

非阻塞

非阻塞和阻塞的概念相对应,指在不能立刻得到结果之前,该调用不会阻塞当前线程。

两者的最大区别在于被调用方在收到请求到返回结果之前的这段时间内,调用方是否一直在等待。
阻塞是指调用方一直在等待而且别的事情什么都不做;非阻塞是指调用方先去忙别的事情。

举例

以小明下载文件为例,对上述概念做一梳理:

1)同步阻塞

同步阻塞:小明一直盯着下载进度条,到 100% 的时候就完成。

同步:等待下载完成通知;
阻塞:等待下载完成通知过程中,不能做其他任务处理;

2)同步非阻塞

同步非阻塞:小明提交下载任务后就去干别的,每过一段时间就去瞄一眼进度条,看到 100% 就完成。

同步:等待下载完成通知;
非阻塞:等待下载完成通知过程中,去干别的任务了,只是时不时会瞄一眼进度条;【小明必须要在两个任务间切换,关注下载进度】

3)异步阻塞

异步阻塞:小明换了个有下载完成通知功能的软件,下载完成就“叮”一声。不过小明仍然一直等待“叮”的声音。

异步:下载完成“叮”一声通知;
阻塞:等待下载完成“叮”一声通知过程中,不能做其他任务处理;

4)异步非阻塞

异步非阻塞:仍然是那个会“叮”一声的下载软件,小明提交下载任务后就去干别的,听到“叮”的一声就知道完成了。
异步:下载完成“叮”一声通知;
非阻塞:等待下载完成“叮”一声通知过程中,去干别的任务了,只需要接收“叮”声通知。

1.2 Linux IO模型

IO执行的两个阶段

一个输入操作时,数据并不会直接拷贝到程序的程序缓冲区,通常包括两个不同的阶段:

  • 1)等待数据准备好;(内核准备数据)

  • 2)从内核向进程复制数据 (从内核复制数据到用户进程)

实际应用程序在系统调用完成上面的 2 步操作时,根据

(调用方)调用方式的阻塞、非阻塞,

(被调用方)操作系统在处理应用程序请求时,处理方式的同步、异步处理的不同,

可以分为 5 种 I/O 模型

Linux的5种IO模型

     

概念说明

优缺点

阻塞I/O

在linux中,默认情况下,所有套接字都是阻塞的。

进程调用一个recvfrom请求,但是它不能立刻收到回复,直到数据返回,然后将数据从内核空间复制到程序空间。

阻塞IO:

在IO执行的两个阶段中,进程都处于blocked(阻塞)状态,在等待数据返回的过程中不能做其他的工作,只能阻塞的等在那里。

优点:程序简单,在阻塞等待数据期间进程/线程挂起,基本不会占用 CPU 资源。
缺点:每个连接需要独立的进程/线程单独处理,当并发请求量大时为了维护程序,内存、线程切换开销较大,这种模型在实际生产中很少使用。

非阻塞I/O

在非阻塞式 I/O 模型中,应用程序把一个套接字设置为非阻塞时,就是告诉内核,当所请求的 I/O 操作无法完成时,不要将进程睡眠,而是返回一个错误。应用程序基于 I/O 操作函数将不断的轮询数据是否已经准备好,如果没有准备好,继续轮询,直到数据准备好为止。

非阻塞IO:

在非阻塞状态下,IO执行的等待阶段并不是完全的阻塞的,但是第二个阶段依然处于一个阻塞状态。

优点:不会阻塞在内核的等待数据过程,每次发起的 I/O 请求可以立即返回,不用阻塞等待,实时性较好。
缺点:轮询将会不断地询问内核,这将占用大量的 CPU 时间,系统资源利用率较低,所以一般 Web 服务器不使用这种 I/O 模型。

I/O多路复用

(select、poll、

epoll)

在 I/O 复用模型中,会用到 select 或 poll 函数或 epoll 函数(Linux 2.6 以后的内核开始支持),这两个函数也会使进程阻塞,但是和阻塞 I/O 有所不同。
这两个函数可以同时阻塞多个 I/O 操作,而且可以同时对多个读操作,多个写操作的 I/O 函数进行检测,直到有数据可读或可写时,才真正调用 I/O 操作函数。

IO多路复用:

单个进程/线程可以同时处理多个网络连接的IO。

它的基本原理就是不再由应用程序自己监视连接,取而代之由内核替应用程序监视文件描述符。

优点:可以基于一个阻塞对象,同时在多个描述符上等待就绪,而不是使用多个线程(每个文件描述符一个线程),这样可以大大节省系统资源。
缺点:当连接数较少时效率相比多线程+阻塞 I/O 模型效率较低,可能延迟更大,因为单个连接处理需要 2 次系统调用,占用时间会有增加。

信号驱动式I/O

在信号驱动式 I/O 模型中,应用程序使用套接口进行信号驱动 I/O,并安装一个信号处理函数,进程继续运行并不阻塞。
当数据准备好时,进程会收到一个 SIGIO 信号,可以在信号处理函数中调用 I/O 操作函数处理数据。

 

优点:线程并没有在等待数据时被阻塞,可以提高资源的利用率。
缺点:信号 I/O 在大量 IO 操作时可能会因为信号队列溢出导致没法通知。

异步I/O模型

应用程序告知内核启动某个操作,并让内核在整个操作(包括将数据从内核拷贝到应用程序的缓冲区)完成后通知应用程序。

 

优点:异步 I/O 能够充分利用 DMA 特性,让 I/O 操作与计算重叠。
缺点:要实现真正的异步 I/O,操作系统需要做大量的工作。目前 Windows 下通过 IOCP 实现了真正的异步 I/O。

而在 Linux 系统下,Linux 2.6才引入,目前 AIO 并不完善,因此在 Linux 下实现高并发网络编程时都是以 IO 复用模型模式为主。

五种IO模型比较

从上图中我们可以看出,越往后,阻塞越少,理论上效率也是最优。

前四种I/O模型都是同步I/O操作,他们的区别在于第一阶段,而他们的第二阶段是一样的:在数据从内核复制到应用缓冲区期间(用户空间),进程阻塞于recvfrom调用。

相反,异步I/O模型在这等待数据和接收数据的这两个阶段里面都是非阻塞的,可以处理其他的逻辑用户进程将整个IO操作交由内核完成,内核完成后会发送通知。在此期间,用户进程不需要去检查IO操作的状态,也不需要主动的去拷贝数据。

1.3 JDK IO发展(BIO--->NIO--->AIO)

 

时间点

概念

同步/异步?阻塞/非阻塞?

代码示例

client数:I/O线程数

API使用难度

吞吐量

总结下

BIO

jdk1.4之前,源码在java.net包下面

Blocking IO

阻塞IO

同步、阻塞

见下面

1:1

或M:N (线程池)

简单

ServerSocket

在活动连接数不是特别高(小于单机1000)的情况下,这种模型是比较不错的,可以让每一个连接专注于自己的 I/O 并且编程模型简单,也不用过多考虑系统的过载、限流等问题。线程池本身就是一个天然的漏斗,可以缓冲一些系统处理不了的连接或请求。但是,当面对十万甚至百万级连接的时候,传统的 BIO 模型是无能为力的。因此,我们需要一种更高效的 I/O 处理模型来应对更高的并发量。

NIO

jdk1.4引入(2002年),源码在java.nio包下面

Non-Blocking IO

非阻塞IO

同步、非阻塞

见下面

M:1

复杂

ServerSocketChannerl

Selector

SelectionKey

ByteBuffer

对于高负载、高并发的(网络)应用,应使用 NIO 的非阻塞模式来开发。

AIO

jdk1.7引入(2011年)

Asynchronous IO

异步IO

异步、非阻塞

M:0

复杂

 

BIO代码示例(未使用Netty的阻塞网络编程)

public class PlainOioServer {
public void serve(int port) throws IOException {
final ServerSocket socket = new ServerSocket(port);
try {
for(;;) {
final Socket clientSocket = socket.accept();
System.out.println("Accepted connection from " + clientSocket);
//每次和一个client建立连接,都要创建一个线程。
//client数:线程数= 1:1
new Thread(new Runnable() {
@Override
public void run() {
OutputStream out;
try {
out = clientSocket.getOutputStream();
out.write("Hi!\r\n".getBytes(Charset.forName("UTF-8")));
out.flush();
clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
clientSocket.close();
} catch (IOException ex) {
// ignore on close
}
}
}
}).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}

NIO代码示例(未使用Netty的非阻塞网络编程)

public class PlainNioServer {
public void serve(int port) throws IOException {
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false);
ServerSocket ss = serverChannel.socket();
InetSocketAddress address = new InetSocketAddress(port);
ss.bind(address);
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
final ByteBuffer msg = ByteBuffer.wrap("Hi!\r\n".getBytes());
for (;;){
try {
selector.select();
} catch (IOException ex) {
ex.printStackTrace();
//handle exception
break;
}
Set<SelectionKey> readyKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = readyKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
try {
if (key.isAcceptable()) {
ServerSocketChannel server =
(ServerSocketChannel) key.channel();
SocketChannel client = server.accept();
client.configureBlocking(false);
client.register(selector, SelectionKey.OP_WRITE |
SelectionKey.OP_READ, msg.duplicate());
System.out.println(
"Accepted connection from " + client);
}
if (key.isWritable()) {
SocketChannel client =
(SocketChannel) key.channel();
ByteBuffer buffer =
(ByteBuffer) key.attachment();
while (buffer.hasRemaining()) {
if (client.write(buffer) == 0) {
break;
}
}
client.close();
}
} catch (IOException ex) {
key.cancel();
try {
key.channel().close();
} catch (IOException cex) {
// ignore on close
}
}
}
}
}
}

AIO代码示例(略)

1.5 Netty与NIO

1.5.1 Netty对3种I/O模式的支持

问题1: 为什么Netty仅支持NIO了?

为什么不建议使用BIO(阻塞IO)?

连接数高的情况下:阻塞---->耗资源、效率低

为什么删除掉已经做好的AIO支持?

Netty5支持AIO,但是被废弃。

  • Windows上AIO实现成熟,但是Windows很少用来做服务器

  • Linux常用来做服务器,但是AIO实现不够成熟(Linux内核2.6才引入AIO)

  • Linux下AIO相比较NIO性能提升不明显

问题2:为什么Netty有多种NIO实现?

不管是jdk还是netty的版本,都是直接调用了linux的epoll来提供IO多路复用。

通用NIO实现(common)在Linux下也是使用epoll,为什么要Netty单独实现epoll?

原因:自己实现的更好

  • netty暴露了更多的可控参数,例如

    • JDK的NIO默认实现中epoll是水平触发

    • Netty中epoll是边缘触发(默认)和水平触发可切换

  • Netty实现的垃圾回收更少、性能更好

1.5.2 代码:Netty使用BIO

public class NettyOioServer {
public void server(int port) throws Exception {
final ByteBuf buf = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8")));
//使用OioEventLoopGroup,表示使用BIO
EventLoopGroup group = new OioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
//使用OioServerSocketChannel,表示使用BIO
.channel(OioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(
new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(
ChannelHandlerContext ctx)
throws Exception {
ctx.writeAndFlush(buf.duplicate())
.addListener(
ChannelFutureListener.CLOSE);
}
});
}
});
ChannelFuture f = b.bind().sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
}

1.5.3 代码:Netty使用NIO

public class NettyNioServer {
public void server(int port) throws Exception {
final ByteBuf buf = Unpooled.unreleasableBuffer(Unpooled.copiedBuffer("Hi!\r\n", Charset.forName("UTF-8")));
//使用NioEventLoopGroup,表示使用NIO
NioEventLoopGroup group = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(group)
//使用NioServerSocketChannel,表示使用NIO
.channel(NioServerSocketChannel.class)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch)
throws Exception {
ch.pipeline().addLast(
new ChannelInboundHandlerAdapter() {
@Override
public void channelActive(
ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush(buf.duplicate())
.addListener(
ChannelFutureListener.CLOSE);
}
});
}
}
);
ChannelFuture f = b.bind().sync();
f.channel().closeFuture().sync();
} finally {
group.shutdownGracefully().sync();
}
}
}

参考资料

https://blog.csdn.net/z_ryan/article/details/80873449 linux5种IO模型

http://www.52im.net/thread-1935-1-1.html 高性能网络编程(五):一文读懂高性能网络编程中的I/O模型

https://juejin.im/post/5d46ce64f265da03e05af722 Netty中的epoll实现

https://github.com/Snailclimb/JavaGuide/blob/master/docs/java/BIO-NIO-AIO.md BIO NIO AIO

iteye.com/blog/m635674608-2171397 Java NIO BIO AIO

https://www.cnblogs.com/bozzzhdz/p/9982448.html

Netty学习之IO模型的更多相关文章

  1. 5月2日 python学习总结 IO模型

    IO模型 1.阻塞IO 2.非阻塞IO 3.多路复用IO 4.异步IO 一.阻塞IO blocking IO的特点就是在IO执行的两个阶段(等待数据和拷贝数据两个阶段)都被block了. 实际上,除非 ...

  2. 聊聊Netty那些事儿之从内核角度看IO模型

    从今天开始我们来聊聊Netty的那些事儿,我们都知道Netty是一个高性能异步事件驱动的网络框架. 它的设计异常优雅简洁,扩展性高,稳定性强.拥有非常详细完整的用户文档. 同时内置了很多非常有用的模块 ...

  3. [编织消息框架][网络IO模型]BIO

    既然跟网络内容有关就不得不学习网络IO模型,时代在进步,技术也在进步,采取使用那种网络IO模型就已经确定应用程序规模 阻塞IO(blocking IO) 在linux中,默认情况下所有的socket都 ...

  4. Netty学习(1):IO模型之BIO

    概述 Netty其实就是一个异步的.基于事件驱动的框架,其作用是用来开发高性能.高可靠的IO程序. 因此下面就让我们从Java的IO模型来逐步深入学习Netty. IO模型 IO模型简单来说,就是采用 ...

  5. Netty学习(2):IO模型之NIO初探

    NIO 概述 前面说到 BIO 有着创建线程多,阻塞 CPU 等问题,因此为解决 BIO 的问题,NIO 作为同步非阻塞 IO模型,随 JDK1.4 而出生了. 在前面我们反复说过4个概念:同步.异步 ...

  6. 2. 彤哥说netty系列之IO的五种模型

    你好,我是彤哥,本篇是netty系列的第二篇. 欢迎来我的公从号彤哥读源码系统地学习源码&架构的知识. 简介 本文将介绍linux中的五种IO模型,同时也会介绍阻塞/非阻塞与同步/异步的区别. ...

  7. 深入了解Netty【四】IO模型

    引言 IO模型就是操作数据输入输出的方式,在Linux系统中有5大IO模型:阻塞式IO模型.非阻塞式IO模型.IO复用模型.信号驱动式IO模型.异步IO模型. 因为学习Netty必不可少的要了解IO多 ...

  8. Netty学习三:线程模型

    1 Proactor和Reactor Proactor和Reactor是两种经典的多路复用I/O模型,主要用于在高并发.高吞吐量的环境中进行I/O处理. I/O多路复用机制都依赖于一个事件分发器,事件 ...

  9. NetCore Netty 框架 BT.Netty.RPC 系列随讲 二 WHO AM I 之 NETTY/NETTY 与 网络通讯 IO 模型之关系?

    一:NETTY 是什么? Netty 是什么?  这个问题其实百度上一搜一堆. 这是官方话的描述:Netty 是一个基于NIO的客户.服务器端编程框架,使用Netty 可以确保你快速和简单的开发出一个 ...

随机推荐

  1. 项目测试环境自动化部署[jenkins前后端配置、Nginx配置]

    持续部署:关注点在于项目功能部署到服务器后可以正常运行,为下一步测试环节或最终用户正式使用做准备.(问题点:一个环节有问题,其他环节跟着有问题) 持续集成:关注点是在于尽早发现项目整体运行问题,尽早解 ...

  2. .NET Core集成SkyWalking+SkyAPM-dotne实现分布式链路追踪

    .NET Core集成SkyWalking+SkyAPM-dotnet实现分布式链路追踪 SkyWalking是一款APM(应用性能管理),其他的还有Cat.Zipkin.Pinpoint等. 随着微 ...

  3. Springcloud之gateway配置及swagger集成

    前言 关于引入gateway的好处我网上找了下: 性能:API高可用,负载均衡,容错机制. 安全:权限身份认证.脱敏,流量清洗,后端签名(保证全链路可信调用),黑名单(非法调用的限制). 日志:日志记 ...

  4. jsonp使用post方法

    来源https://www.jb51.net/article/68980.htm

  5. 自定义3D地图

    基于echarts的3D地图进行,直接将这代码粘贴到echarts的demo中即可呈现效果 var mygeo = { // 标准的geojson格式 "type": " ...

  6. Angular学习知识点记录

    问:版本直接跳转到Angular4? 答:为了遵循严格的版本策略.在angular2.x的时候,angular route的版本已经是版本3了.因此为了版本统一,angular直接从2跳到了4,.参考 ...

  7. 如何自定义Kubernetes资源

    目前最流行的微服务架构非Springboot+Kubernetes+Istio莫属, 然而随着越来越多的微服务被拆分出来, 不但Deploy过程boilerplate的配置越来越多, 且繁琐易错, 维 ...

  8. Kafka客户端编程入门介绍

    1.maven依赖 <dependency> <groupId>org.apache.kafka</groupId> <artifactId>kafka ...

  9. [JVM工具(1)] 堆栈检查利器jstat的使用

    jstat 可以检查 JVM 整体的运行情况,可以看到 新生代,老年代等的内存使用情况,以及GC 次数和耗时 命令格式 如 jstat -<option> [-t] [-h<line ...

  10. Git - 简单的使用与Github

    Github: Following the instructions to create repo. Git on Linux(centos): download the latest GIT and ...