NIO 源码分析(03) 从 BIO 到 NIO

Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)

一、NIO 三大组件 Channels、Buffers、Selectors

1.1 Channel 和 Buffer

基本上,所有的 IO 在 NIO 中都从一个 Channel 开始。Channel 有点象流。 数据可以从 Channel 读到 Buffer 中,也可以从 Buffer 写到 Channel 中。这里有个图示:

总结: Channel 和 Buffer 在 NIO 并不是新的东西:Channel 的本质就是 Socket,Buffer 的本质就是 byte[]。在 BIO 时代,BufferedInputStream 就是一个缓冲流。

1.2 Selector

Selector 允许单线程处理多个 Channel。如果你的应用打开了多个连接(通道),但每个连接的流量都很低,使用 Selector 就会很方便。例如,在一个聊天服务器中。

这是在一个单线程中使用一个 Selector 处理 3 个 Channel 的图示:

总结: Selector 在是 NIO 的核心,有了 Selector 模型,一个线程就可以处理多个 Channel 了。

1.3 Linux IO 和 NIO 编程的区别

(1) Linux IO 网络编程

int listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
listen(listenfd, BACKLOG); socklen_t cliaddr_len = sizeof(client_addr);
int clientfd = accept(listenfd, (struct sockaddr*)&client_addr, &cliaddr_len);

(2) Linux NIO 网络编程

int listenfd = socket(AF_INET, SOCK_STREAM, 0);
bind(listenfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr));
listen(listenfd, BACKLOG); // select 模型处理过程
// 1. 初始化套接字集合,添加监听 socket 到这个集合
FD_ZERO(&totalSet);
FD_SET(listenfd, &totalSet);
maxi = listenfd; while(1) {
// 2. 将集合的一个拷贝传递给 select 函数。当有事件发生时,select 移除未决的 socket 然后返回。
// 也就是说 select 返回时,集合 readSet 中就是发生事件的 readSet
readSet = totalSet;
int nready = select(maxi + 1, &readSet, NULL, NULL, NULL);
if (nready > 0) {
if (FD_ISSET(listenfd, &readSet)) {
cliaddr_len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *) &cliaddr, &cliaddr_len);
printf("client IP: %s\t PORT : %d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port)); FD_SET(connfd, &totalSet);
maxi = connfd;
if (--nready == 0) {
continue;
}
}
}
}

总结: 对比 Linux IO 和 NIO 网络编程可以发现,NIO 相对 BIO 多出来的部分其实是 select 部分,其余的(包括 socket 创建,数据读取等)都是一样的。所以我说 Channel 和 Buffer 是 JDK 层面概念的转换,Selector 才是 NIO 的核心,接下来 NIO 的源码会更多的关注 Selector 模型的分析,Channel 和 Buffer 点到即止。

二、BIO 和 NIO 的区别

2.1 BIO 面向流,NIO 面向缓冲区。

Java NIO 和 IO 之间第一个最大的区别是,IO 是面向流的,NIO 是面向缓冲区的。

Java IO 面向流意味着每次从流中读一个或多个字节,直至读取所有字节,它们没有被缓存在任何地方。此外,它不能前后移动流中的数据。如果需要前后移动从流中读取的数据,需要先将它缓存到一个缓冲区。

Java NIO 的缓冲导向方法略有不同。数据读取到一个它稍后处理的缓冲区,需要时可在缓冲区中前后移动。这就增加了处理过程中的灵活性。但是,还需要检查是否该缓冲区中包含所有您需要处理的数据。而且,需确保当更多的数据读入缓冲区时,不要覆盖缓冲区里尚未处理的数据。

面向流,面向缓冲区,这是 Java 中的概念,和操作系统无关。

(1) Java IO 【SocketInputStream】

/**
* Java IO 直接对读取的数据进行操作
* 1. socketRead(native) 函数中获取相当长度的数据,然后直接对这块数据进行了操作
*/
int read(byte b[], int off, int length, int timeout) throws IOException { // acquire file descriptor and do the read
FileDescriptor fd = impl.acquireFD();
try {
// native函数,这里从内核态中读取数据到数组 b 中
n = socketRead(fd, b, off, length, timeout);
if (n > 0) {
return n;
}
} catch (ConnectionResetException rstExc) {
} finally {
impl.releaseFD();
}
}

(2) Java NIO 【DatagramChannelImpl】

/**
* Java NIO 每次读取的数据放在该内存中,然后对该内存进行操作,增加了处理数据的灵活性
* 1. Util.getTemporaryDirectBuffer(newSize) 申请了一块堆外内存
* 2. receiveIntoNativeBuffer(native) 将数据读取到堆外内存中
* 3. dst.put(bb) 将数据从该内存中读取到内存块 dst 中
* 4. dst 就一个共享的内存块,可以对该内存进行各种操作,但也要注意一些问题,如数据覆盖
*/
private int receive(FileDescriptor fd, ByteBuffer dst)
throws IOException {
int pos = dst.position();
int lim = dst.limit();
assert (pos <= lim);
int rem = (pos <= lim ? lim - pos : 0);
if (dst instanceof DirectBuffer && rem > 0)
return receiveIntoNativeBuffer(fd, dst, rem, pos); // 申请一块 newSize 大小的缓冲区块
int newSize = Math.max(rem, 1);
ByteBuffer bb = Util.getTemporaryDirectBuffer(newSize);
try {
// 数据读取到缓冲区中,buffer 可以做标记,操作指针等
int n = receiveIntoNativeBuffer(fd, bb, newSize, 0);
bb.flip();
if (n > 0 && rem > 0)
dst.put(bb);
return n;
} finally {
Util.releaseTemporaryDirectBuffer(bb);
}
}

可以看到,第一段代码中一次性从 native 函数中获取相当长度的数据,然后直接对这块数据进行了操作。

而第二段代码中 Util.getTemporaryDirectBuffer(newSize); 申请了一块堆外内存,每次读取的数据放在该内存中,然后对该内存进行操作。

一般说法面向缓存相对于面向流的好处在于增加了处理数据的灵活性,当然也增加了操作的复杂度,比如当更多数据取入时,是否会覆盖前面的数据等


每天用心记录一点点。内容也许不重要,但习惯很重要!

NIO 源码分析(03) 从 BIO 到 NIO的更多相关文章

  1. NIO 源码分析(02-2) BIO 源码分析 Socket

    目录 一.BIO 最简使用姿势 二.connect 方法 2.1 Socket.connect 方法 2.2 AbstractPlainSocketImpl.connect 方法 2.3 DualSt ...

  2. NIO 源码分析(02-1) BIO 源码分析

    目录 一.BIO 最简使用姿势 二.ServerSocket 源码分析 2.1 相关类图 2.2 主要属性 2.3 构造函数 2.4 bind 方法 2.5 accept 方法 2.6 总结 NIO ...

  3. NIO 源码分析(05) Channel 源码分析

    目录 一.Channel 类图 二.begin 和 close 是什么 2.1 AbstractInterruptibleChannel 中的 begin 和 close 2.2 Selector 中 ...

  4. NIO 源码分析(04) 从 SelectorProvider 看 JDK SPI 机制

    目录 一.SelectorProvider SPI 二.SelectorProvider 加载过程 2.1 SelectorProvider 加载 2.2 Windows 下 DefaultSelec ...

  5. NIO 源码分析(01) NIO 最简用法

    目录 一.服务端 二.客户端 NIO 源码分析(01) NIO 最简用法 Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html) J ...

  6. JDK1.8源码分析03之idea搭建源码阅读环境

    序言:上一节说了阅读源码的顺序,有了一个大体的方向,咱们就知道该如何下手.接下来,就要搭建一个方便阅读源码及debug的环境.有助于跟踪源码的调用情况. 目前新开发的项目, 大多数都是基于JDK1.8 ...

  7. NIO源码分析:SelectionKey

    SelectionKey SelectionKey,选择键,在每次通道注册到选择器上时都会创建一个SelectionKey储存在该选择器上,该SelectionKey保存了注册的通道.注册的选择器.通 ...

  8. 【转】jQuery源码分析-03构造jQuery对象-源码结构和核心函数

    作者:nuysoft/高云 QQ:47214707 EMail:nuysoft@gmail.com 毕竟是边读边写,不对的地方请告诉我,多多交流共同进步.本章还未写完,完了会提交PDF. 前记: 想系 ...

  9. jQuery源码分析-03构造jQuery对象-源码结构和核心函数

    3. 构造jQuery对象 3.1源码结构 先看看总体结构,再做分解: (function( window, undefined ) { var jQuery = (function() { // 构 ...

随机推荐

  1. Linux下tomcat启动慢,阻塞

    声明:本文为转载,请尊重版权,原文地址: https://www.cnblogs.com/songjinju/p/7505564.html 这两天在linux部署完tomcat以后,发现每次启动都非常 ...

  2. ubuntu php多版本共存切换

    做开发时,由于本机开发的php版本跟线上发布的php版本不一致,很容易在上线后,发现因版本的影响导致一些bug,但又不想重新去换本机的php版本,那么多版本共存就很方便了!有必要时,切换到指定版本测试 ...

  3. 点读系列《jmeter官方用户手册》

    官网:http://jmeter.apache.org/usermanual/ 说明:十八元件.十九属性.二十函数,涉及清单内容暂未仔细阅读,个人觉得一是仅供使用参考,二是适合单独写文章来解读 一.让 ...

  4. Django框架(十八)—— CBV源码分析、restful规范、restframework框架

    目录 CBV源码分析.restful规范.restframework框架 一.CBV源码分析 1.url层的使用CBV 2.as_view方法 3.view方法 4.dispatch方法(可以在视图层 ...

  5. Tika教程

    Apache Tika 是什么? Apache Tika用于文件类型检测和从各种格式的文件内容提取的库. 在内部,Tika使用现有的各种文件解析器和文档类型的检测技术来检测和提取数据. 使用Tika, ...

  6. yum update过程中失败后再次执行出现“xxxx is a duplicate with xxxx”问题

    问题现象: 解决办法: 利用yum-uitls中的工具package-cleanup指令,使用方法见下图,具体可通过man package-cleanup查询 列出重复的rpm包        pac ...

  7. 高级UI晋升之自定义View实战(六)

    更多Android高级架构进阶视频学习请点击:https://space.bilibili.com/474380680本篇文章将从Android 自定义属性动画&Camera动画来介绍自定义V ...

  8. leetcode.数组.667优美的排列II-Java

    1. 具体题目 给定两个整数 n 和 k,你需要实现一个数组,这个数组包含从 1 到 n 的 n 个不同整数,同时满足以下条件:① 如果这个数组是 [a1, a2, a3, ... , an] ,那么 ...

  9. codeforces 1A

    题目大意: 就是在一块长方形地面上铺瓷砖,然后一共要用多少块瓷砖,一块瓷砖被割开后只能用一次,输入长,宽,以及瓷砖边长,求一共需要多少块瓷砖: 基本思路: 这里有个技巧:就长来说,需要(n+k-1)/ ...

  10. SqlServer 将表里面的值赋给变量

    @@c_beiz varchar(500) select top 1 @c_beiz = c_beiz from g_billd_qh_tmp where u_id=''+@uid+'' and io ...