java socket 事件触发发送数据_Java网络学习必备:I/O模型和Reactor模式

“网络 I/O 在系统学习、性能优化和开发中越来越重要,本文带你了解 4 种 I/O 模型和 3 种 Reactor 模式”

I/O模型

下图所示,同步和异步,阻塞和非阻塞,两两结合一共有 4 种I/O模型

36eeb3a119e604607e34a5ee1a916c10.png

阻塞和非阻塞:根据程序是否阻塞自身运行来区分的。

  • 阻塞:应用程序在执行I/O操作后,如果没有获得响应,就会阻塞当前线程,不能执行其他任务。
  • 非阻塞:是指应用程序在执行I/O操作后,不会阻塞当前的线程,可以继续执行其他的任务。

同步和异步:根据 I/O 响应的通知方式的不同。

  • 同步 I/O:收到 I/O 请求后,系统不会立刻响应应用程序;等到处理完成,系统才会通过系统调用的方式,告诉应用程序 I/O 结果。
  • 异步 I/O:收到 I/O 请求后,系统会先告诉应用程序 I/O 请求已经收到,随后再去异步处理;等处理完成后,系统再通过事件通知的方式,告诉应用程序结果。

同步阻塞 (Synchronouse blocking I/O)

9d7f821f9b8f56698c2ead65e2e597b7.png

如上图所示,当应用程序发送读取数据的请求,内核接收到请求开始读取数据,一直到数据读完(或者遇到错误)返回给应用程序,应用程序一直是阻塞的。

这种场景应用程序阻塞是不占用CPU资源的。

同步非阻塞(Synchronous non-blocking I/O)

103d54a82dd804d2a1d646bbf44c996a.png

这里应用程序发送读取请求后,内核会立马返回( EAGAIN/EWOULDBLOCK ),表明数据还未就绪,稍后再试。这个时候就需要应用程序不断轮询,直到内核返回数据给应用程序。

这种方式是非常低效的,应用程序要不断轮询,需要占用 CPU 资源。并且这个时候如果内核已经读取完数据,然后程序的下一次轮询还未开始,这就会造成时间的浪费,对系统吞吐量造成影响。

异步阻塞(Asynchronous blocking I/O)

f0569de6ec59e2275e3d9a61953e58c8.png

这里应用程序会将 socket 注册到 select 上,当应用程序调用 select 的时候就会被阻塞,而 Kernel 同时会监听 select 上的 socket 有数据可读 select 就会返回,然后程序读取数据。

这种方式的优势就在于可以处理大量的连接,对于连接数比较少的不一定比同步阻塞性能好。

在 Tomcat、Netty 中这种方式都叫做 NIO。也有叫 I/O 多路复用(I/O mutiplexing) ,这里 select 可以同时管理多个 socket ,对应 Linux 的实现有:select、poll、epoll

异步非阻塞(Asynchronous non-blocking I/O)

0519dafee6ba7f6be26437aa767b0ec5.png

应用程序向内核请求读取数据,内核会立马告诉应用程序开始读取数据,然后应用程序可以处理其他事情。当内核读取完数据,并将数据 copy 到用户空间后,会通知应用程序数据已经读取完成。

这种模式一般我们也称作 AIO ,这种方式理论上比 NIO 要好,Netty 为什么不支持呢?(Netty 曾今支持过后来又移除了)

  • AIO 在 Windows 上比较成熟,但很少用来做服务器。
  • AIO 在 Linux 下实现并不成熟。
  • Linux 下 AIO 先比较于 NIO 性能提升并不明显。

Reactor模式

在讲Reactor模式前,我们先回顾下传统的网络请求处理方式:

75b67325d31331d3991e207da6e126ad.png

每接收一个客户端的请求,服务端都会创建一个线程来处理。

这种就是我们上面介绍的同步阻塞 I/O 模式,开发维护成本比较小,适合连接数比较少的服务。

既然每个请求分配一个线程不合适,怎样才能一个线程中处理多个请求?

我们可采用分治的思想,将处理流程拆分成更小的任务,减少每个线程的阻塞时间,基于事件驱动 (异步阻塞 I/O)。

接下来我们看看 Reactor 的定义

  • Reactor:监听和分发 I/O 事件。
  • Handlers:处理分配的事件。

单线程 Reactor 模式

2b5a01395f79fa6925062b1b6d592ad0.png

这里 Reactor 是单线程的,接收所有的客户端请求,连接请求分配给 acceptor,数据处理分配给处理的线程。

线程池处理业务逻辑

5c898d5cbd50393dca03fb08e31e7928.png

将业务处理线程与 I/O 处理拆分开,这样就可以避免 Reactor 线程由于业务处理造成阻塞,同样业务线程也不会受 Reactor 网络读取和发送的影响。

多个 Reactor

84b1d74e016a48570b12ef42d82e6304.png
  • mainReactor:负责监听收客户端的连接。对应 Netty 中的 boosGroup,一般一个线程就够了。
  • subReactor:负责读取发送数据,将请求分配给业务线程池。对应 Netty 中的 workerGroup。

java.nio

这里简单介绍下 java.nio 对 Reactor 模式支持的几个概念

  • Channel:socket 连接,文件连接等,用户数据传输,支持双向传输,Channel 可以将读取的网络数据写到 Buffer 中,也可以将 Buffer 数据发送到网络中。
  • Buffers: 存储数据,底层数组实现
  • Selectors:获取关注的 I/O 事件。每个 Channel 需要注册到 Selector 上,并标明关注的 I/O 事件,一个 Selector 可以同时注册多个 Channel。
  • SelectonKeys:保存的 I/O 事件和对应 Channel。

参考文章

https://developer.ibm.com/technologies/linux/articles/l-async/

•http://gee.cs.oswego.edu/dl/cpjslides/nio.pdf

e1e14f075a574adb3a0fed70d2bc3ebe.gif
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值