IO多路复用模型

IO多路复用需要OS的支持,IO多路复用模型中,引入了一种新的系统调用,查询IO的就绪状态。

在Linux系统中,JavaNIO的Selector#select() 方法对应的系统调用为select/epoll系统调用。

通过该系统调用,一个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是内核缓冲区可读/可写),内核能够将就绪的状态返回给应用程序。随后,应用程序根据就绪的状态,进行相应的IO系统调用。

目前支持IO多路复用的系统调用,有select、epoll等等。select系统调用,几乎在所有的操作系统上都有支持,具有良好的跨平台特性。epoll是在Linux 2.6内核中提出的,是select系统调用的Linux增强版本。

在IO多路复用模型中通过select/epoll系统调用单个应用程序的线程,可以不断地轮询成百上千的socket连接,当某个或者某些socket网络连接有IO就绪的状态,就返回对应的可以执行的读写操作。

IO多路复用模型的特点:

IO多路复用模型的IO涉及两种系统调用(System Call),一种是IO操作,另一种是select/epoll(就绪查询)。

IO多路复用模型建立在操作系统的基础设施之上,即操作系统的内核必须能够提供多路分离的系统调用select/epoll。

多路复用IO也需要轮询。负责select/epoll状态查询调用的线程,需要不断地进行select/epoll轮询,查找出达到IO操作就绪的socket连接。IO多路复用模型与同步非阻塞IO模型是有密切关系的。

对于注册在选择器上的每一个可以查询的socket连接,一般都设置成为同步非阻塞模型。仅是这一点,对于用户程序而言是无感知的。

IO多路复用模型的优点:

与一个线程维护一个连接的阻塞IO模式相比,使用select/epoll的最大优势在于,一个选择器查询线程可以同时处理成千上万个连接(Connection)。系统不必创建大量的线程,也不必维护这些线程,从而大大减小了系统的开销。Java语言的NIO(New IO)技术,使用的就是IO多路复用模型。在Linux系统上,使用的是epoll系统调用。

IO多路复用模型的缺点:

本质上,select/epoll系统调用是阻塞式的,属于同步IO。都需要在读写事件就绪后,由系统调用本身负责进行读写,也就是说这个读写过程是阻塞的。

⭐️注意:轮询的效率比较低,在硬件和操作系统层有个概念叫做“中断”, 这种机制可以在设备准备好的情况下,主动通知等待的程序。

Selector


Selector selector = Selector.open();

调用栈如下,在我的笔记本上open() 方法就是返回一个WindowsSelectorImpl 实例,在其构造方法中调用了一些native 方法。

<init>:126, WindowsSelectorImpl (sun.nio.ch)
openSelector:44, WindowsSelectorProvider (sun.nio.ch)
open:227, Selector (java.nio.channels)
startServer:63, NioReceiveServer (com.yh.stu.nio.socket)
main:177, NioReceiveServer (com.yh.stu.nio.socket)

ServerSocketChannel

 // 2、获取通道ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();// 3.设置为非阻塞serverSocketChannel.configureBlocking(false);// 4、绑定连接ServerSocket serverSocket = serverSocketChannel.socket();InetSocketAddress address= new InetSocketAddress(NioDemoConfig.SOCKET_SERVER_PORT);serverSocket.bind(address);// 5、将通道注册到选择器上,并注册的IO事件为:“接收新连接”Print.tcfo("serverSocketChannel is linstening...");serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

ServerSocketChannel.open() 创建一个ServerSocketChannel 对象(实际类型是 ServerSocketChannelImpl)对象,如下图

# new ServerSocketChannelImpl(SelectorProviderImpl)
openServerSocketChannel:56, SelectorProviderImpl (sun.nio.ch)
open:108, ServerSocketChannel (java.nio.channels)
run:120, PipeImpl$Initializer$LoopbackConnector (sun.nio.ch)
run:76, PipeImpl$Initializer (sun.nio.ch)
run:61, PipeImpl$Initializer (sun.nio.ch)
doPrivileged:-1, AccessController (java.security)
<init>:171, PipeImpl (sun.nio.ch)
openPipe:50, SelectorProviderImpl (sun.nio.ch)
open:155, Pipe (java.nio.channels)
<init>:127, WindowsSelectorImpl (sun.nio.ch)
openSelector:44, WindowsSelectorProvider (sun.nio.ch)
open:227, Selector (java.nio.channels)
startServer:63, NioReceiveServer (com.yh.stu.nio.socket)
main:177, NioReceiveServer (com.yh.stu.nio.socket)

serverSocketChannel.socket() 方法,在ServerSocketChannel 中创建一个SocketChannel 对象 (实际类型是ServerSocketAdaptor)

socket:110, ServerSocketChannelImpl (sun.nio.ch)
createServerSocketChannel:110, NioReceiveServer (com.yh.stu.nio.socket)
startServer:65, NioReceiveServer (com.yh.stu.nio.socket)
main:177, NioReceiveServer (com.yh.stu.nio.socket)

serverSocket.bind(address) 方法最终调用的是 ServerSocketChannelbind(..) 方法,调用栈和示例图如下:

bind:220, ServerSocketChannelImpl (sun.nio.ch)
bind:74, ServerSocketAdaptor (sun.nio.ch)
bind:67, ServerSocketAdaptor (sun.nio.ch)
createServerSocketChannel:113, NioReceiveServer (com.yh.stu.nio.socket)
startServer:65, NioReceiveServer (com.yh.stu.nio.socket)
main:177, NioReceiveServer (com.yh.stu.nio.socket)

| ||
|–|

调用 WindowsSelectorImpl 父类 SelectorImplregister 方法将进行注册,源码如下:

 //sun.nio.ch.SelectorImpl#registerprotected final SelectionKey register(AbstractSelectableChannel ch,int ops,Object attachment){if (!(ch instanceof SelChImpl))throw new IllegalSelectorException();//将`Selector` 和`ServerSocketChannel` 包装成了 `SelectionKeyImpl `对象SelectionKeyImpl k = new SelectionKeyImpl((SelChImpl)ch, this);k.attach(attachment);synchronized (publicKeys) {implRegister(k);}k.interestOps(ops);return k;}// sun.nio.ch.WindowsSelectorImpl#implRegisterprotected void implRegister(SelectionKeyImpl ski) {synchronized (closeLock) {if (pollWrapper == null)throw new ClosedSelectorException();growIfNeeded();channelArray[totalChannels] = ski;ski.setIndex(totalChannels);fdMap.put(ski);keys.add(ski);// 将 `SelectionKeyImpl ski` 放入`HashSet` 中pollWrapper.addEntry(totalChannels, ski);totalChannels++;}}

从源码中不难看出,将SelectorServerSocketChannel 包装成了 SelectionKeyImpl对象,并将 SelectionKeyImpl 放入Set 中,同时SelectionKeyImpl下的属性interestOps 设置成 SelectionKey.OP_ACCEPT

|
其实在ServerSocketChannel 中也有一个数组,用来放 SelectionKey 的引用,方法调用栈和图示如下

addKey:115, AbstractSelectableChannel (java.nio.channels.spi)
register:213, AbstractSelectableChannel (java.nio.channels.spi)
register:280, SelectableChannel (java.nio.channels)
startServer:66, NioReceiveServer (com.yh.stu.nio.socket)
main:177, NioReceiveServer (com.yh.stu.nio.socket)

···

到这里一切准备就绪了,等待客户端来连接

创建一个连接

调用栈如下

<init>:129, SocketChannelImpl (sun.nio.ch)
accept:266, ServerSocketChannelImpl (sun.nio.ch)
createSocketChannel:97, NioReceiveServer (com.yh.stu.nio.socket)
startServer:78, NioReceiveServer (com.yh.stu.nio.socket)
main:177, NioReceiveServer (com.yh.stu.nio.socket)
SocketChannelImpl(SelectorProvider sp,FileDescriptor fd, InetSocketAddress remote)throws IOException{super(sp);this.fd = fd;this.fdVal = IOUtil.fdVal(fd);this.state = ST_CONNECTED;this.localAddress = Net.localAddress(fd);this.remoteAddress = remote;}

深入理解NIO - Selector、ServerSocketChannel、SocketChannel底层原理相关推荐

  1. Java NIO Selector , SelectionKey , SocketChannel , ServerSocketChannel

    一    NIO介绍 1. NIO是非阻塞的 NIO非堵塞应用通常适用用在I/O读写等方面,我们知道,系统运行的性能瓶颈通常在I/O读写,包括对端口和文件的操作上,过去,在打开一个I/O通道后,rea ...

  2. 最容易理解的反射机制的底层原理

    看了好多关于Java反射机制的文章,大多都太过官方,消化起来比较稍显费劲,本篇,我会依据自己的理解去阐述什么是Java的反射机制,反射用在什么地方,以及怎么来使用? 开篇前,我们还是要了解一下,什么是 ...

  3. 深入理解 Spring Cloud 核心组件与底层原理

    点击上方"方志朋",选择"设为星标" 回复"666"获取新整理的面试资料 来源:邋遢的流浪剑客 blog.csdn.net/qq_40378 ...

  4. 深入理解 Spring Cloud 核心组件与底层原理!

    一.Spring Cloud核心组件:Eureka Netflix Eureka Eureka详解 1.服务提供者 2.服务消费者 3.服务注册中心 二.Spring Cloud核心组件:Ribbon ...

  5. springcloud 组件_深入理解 Spring Cloud 核心组件与底层原理

    新人大礼包,30G Java架构资料,免费领取​zhuanlan.zhihu.com 一.Spring Cloud核心组件:Eureka Netflix Eureka Eureka详解 1.服务提供者 ...

  6. 完全理解NIO Selector

    一.Selector是什么 Selector是一个或多个SelectableChannel对象的多路复用器. 二.如何创建一个Selector对象 一个selector对象可以通过调用Selector ...

  7. 深入理解滤波器!降噪的底层原理!滤波器到底是什么?

    高通滤波器电路图如下: 我们知道,交流电频率越高越容易通过电容.高通滤波器中,电容可以让高频信号通过,把低频号拒之门外. 滤波器截止频率计算公式为. 举例:当选择电阻为1000欧,电容为160nf,此 ...

  8. java,从入土到出棺——2.数据结构(从容器(集合等)到底层原理)

    java,从入土到出棺--2.数据结构(从容器(集合等)到底层原理) 1 数据结构 1.1 概述 1.2 数据结构的种类 1.2.1 数组(Array) 1.2.2 栈(Stack) 1.2.3 队列 ...

  9. 【死磕NIO】— 探索 SocketChannel 的核心原理

    Python微信订餐小程序课程视频 https://edu.csdn.net/course/detail/36074 Python实战量化交易理财系统 https://edu.csdn.net/cou ...

最新文章

  1. 百度地图轨迹回放,自定义路书,边走边画线
  2. 关于 TApplication 详解 三 ---- TComponent
  3. signature=0a26d8967069103efeee67346aac0529,Construction of Thinned Gated Single-Assignment Form
  4. Navicat连接MySQL8.0出现乱码的解决方案
  5. Vue.js-Day04-PM【axios(安装、使用)】
  6. Kettle使用_21 分组与分析窗口函数
  7. 从框架源码中学习结构型设计模式
  8. 阿里研究院:2020阿里农产品电商报告
  9. 如何快速从基因组中提取基因、转录本、蛋白、启动子、非编码序列?
  10. PLSA隐变量主题模型的公式推导解惑
  11. 绘图QPainter-字体
  12. 抹机王怎么一键新机_抹机王助手_手机数据修改_抹机王app下载_易玩网
  13. python判断ip是否可以ping通
  14. java虎牙app弹幕_虎牙直播随机弹幕插件(OBSS)
  15. 嵌入式分享合集106
  16. 软件工程网络工程第二次训练(AC代码和详细解释)(C语言描述)
  17. 《数学史概论》读后感
  18. 短信导出工具V1.3
  19. three.js中jsm文件夹的使用
  20. 【Unet系列】(三)Unet++网络

热门文章

  1. Javascript模块化编程系列二: 模块化的标准化(CommonJS AMD)
  2. 客户和顾客是一个意思吗_履约保证金和投标保证金是一个意思吗?
  3. java double的加法_java Double 进行加减乘除
  4. linux查看系统后台,求助,如何查看后台服务
  5. Keil5的仿真调试
  6. Python的第三方库xlwt
  7. orange实现逻辑回归_逻辑回归模型
  8. python超级关系_不可阻挡的超级语言--python
  9. html表头跟随滚动,JS实现table表格固定表头且表头随横向滚动而滚动_心善_前端开发者...
  10. linux下vi编辑器的命令大全,linux下VI编辑器命令大全(超级完整版)