目录

NIO(一、概述)

NIO(二、Buffer)

NIO(三、Channel)

NIO(四、Selector)

Channel

上文说了描述了Buffer的实现机制,那么这个章节就主要描述数据是如何进入缓冲区的,并且又是如何从缓冲区流出的。

类图纵览及核心类概述

  这张图只是简单概括了Channel的类图,当然,Channel的设计远比这个更复杂:例如SelectableChannel还有SocketChannel和ServerSocketChannel的实现,NetworkChannel继承Channel并抽象了更多的方法;例如FileChannel,除了继承AbstractInterruptibleChannel之外,还实现了GatheringByteChannel和ScatteringByteChannel接口。

  • Channel

      我们可以看到,Channel接口本身定义了 close() 和 isOpen() 方法,在继承Channel的接口中,又分别抽象了读通道(ReadableByteChannel)、写通道(WritableByteChannel)及可中断的异步通道(InterruptibleChannel)接口。读写通道自然不必说,下文也会有介绍。

  • InterruptibleChannel

      这里说下InterruptibleChannel,这是一个可以被中断的异步通道,继承了 close() 方法。当一个线程在I/O被阻塞时,另一个线程执行了close()方法,那么阻塞的线程会抛出 AsynchronousCloseException异常。当一个线程在I/O被阻塞时,另一个线程调用阻塞的线程中断(interrupt())方法,那么将会抛出ClosedByInterruptException异常。

  • AbstractInterruptibleChannel

      AbstractInterruptibleChannel抽象类,这是所有可中断通道实现的基类,我们可以看到,FileChannel正是直接继承自它,后文会介绍的SocketChannel和ServerSocketChannel继承自AbstractSelectableChannel抽象类,而AbstractSelectableChannel又继承自SelectableChannel抽象类,SelectableChannel上图就可以看出,同样继承AbstractInterruptibleChannel。我们不得不想一下,为什么FileChannel直接继承AbstractInterruptibleChannel抽象类,而另一个常用的比如SocketChannel却需要继承自SelectableChannel抽象类?此时我们需要只了解SelectableChannel是什么问题就迎刃而解了。

  • SelectableChannel

      第一个章节在概述中提过一个名词“多路复用”,当时也作了简单描述:一个线程通过选择器处理和管理多个通道,利用一个线程的资源去处理多个连接。SelectableChannel正是扮演着其中的一个重要角色。而类似SocketChannel一样实现NetworkChannel接口的通道,这种网络数据的传输尤其在高并发的压力下,让CPU利用率真正体现在处理数据上,而不是频繁上下文切换的开销,使用这种机制能明显提升处理性能,而FileChannel这种对文件操作是绝对不会使用到这种机制的。另外,SelectableChannel在阻塞模型中,每一个I/O操作都会阻塞到它完成为止,在非阻塞模型中,是不会出现阻塞的情况,同样返回的字节数可能少于要求或者干脆没有,是否阻塞我们能通过 isBlocking() 方法查看。

      SelectableChannel是通过选择器(Selector)复用的通道,为了配合与选择器一起使用,可使用 register() 方法,这个方法会返回一个SelectionKey对象,表示已经在选择器里注册了,这个通道会一直保持到注销为止,同时也包括已经分配在这个选择器上的这个通道的资源。一般情况下,通道自己不能直接在通道上注销,我们可以调用之前注册时返回的SelectionKey对象的 cancel() 方法,可以显式注销。另外,阅读代码我们发现,几乎所有的实现方法都有同步控制,所以,在多个并发线程下使用是安全的。

Scatter/Gather

  Channel,它既是缓冲区数据的入口,也是其数据的出口。

  在上面的类图我们可以看到,Channel分别抽象了ReadableByteChannel和WritableByteChannel接口,两个接口各自定义了 read() 和 write() 方法,读取和写入ByteBuffer对象。并在这个基础上又进一步定义了ScatteringByteChannel和GatheringByteChannel接口,同样,这两个接口同样存在继承自ReadableByteChannel和WritableByteChannel接口的read() 和 write() 方法,此外,还分别重载了 read() 和 write() 方法,增加了读取或写入ByteBuffer数组。

  

  • Scattering Reads

      读取数据从一个通道到多个缓冲区,这是简单示意图:

  ByteBuffer   buffer1 = ByteBuffer.allocate(1024);
ByteBuffer buffer2 = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {buffer1, buffer2};
channel.read(buffers);
  • Gathering Reads

      写数据从多个缓冲区到一个通道,这是简单示意图:

  ByteBuffer   buffer1 = ByteBuffer.allocate(1024);
ByteBuffer buffer2 = ByteBuffer.allocate(1024);
ByteBuffer[] buffers = {buffer1, buffer2};
channel.write(buffers);

Channel之间数据传输

  FileChannel的 transferFrom() 和 transferTo() 两个方法实现了通道之间的数据传输,当有一方是FileChannel时,另一方实现了 ReadableByteChannel和WritableByteChannel接口的通道就能够与FileChannel传输数据。目前为止,只有文件通道(FileChannel)能够双向传输。

  • transferFrom() & transferTo()
FileChannel fromChannel = fromFile.getChannel();
FileChannel toChannel = toFile.getChannel();
// transferFrom
toChannel.transferFrom(fromChannel, position, count);
// transferTo
fromChannel.transferTo(position,count,toChannel);

  我们在讲Buffer的时候就已经说了position,意指读写位置,count指的是数据大小。

Channel重要实现

  • FileChannel:操作文件的读写
  • SocketChannel:通过TCP读写网络数据
  • ServerSocketChannel:监听TCP连接,你能利用它创建一个最简单的Web服务器
  • DatagramChannel:通过UDP读写网络数据

SocketChannel & ServerSocketChannel

  创建SocketChannel仅需要调用它的 open() 方法:

  SocketChannel channel = SocketChannel.open();

  事实上,可能在实际使用的时候,我们也会在的句柄对象的处理方法里,使用SelectionKey的 channel() 方法来获取,或者ServerSocketChannel对象的 accept() 方法来获取。

  SelectionKey key = ...
//接受消息处理
SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();
//或者
SocketChannel channel = (SocketChannel) key.channel();

  在我们手动 open() 打开这个socket之后,其实还未连接,这时候我们对这个通道进行I/O操作会抛出NotYetConnectedException。我们可以使用 connect() 方法来连接通道的socket,与此同时,也可以使用 isConnected() 判断是否已连接。如果这个Socket在非阻塞模型中,就需要 finishConnect() 来确定连接已完成,也可以通过 isConnectionPending() 来测定该通道的连接是否正在进行中。

  // 设置非阻塞
channel.configureBlocking(false);
if(channel.isConnected()) {
channel.connect(new InetSocketAddress(8080));
while (channel.finishConnect()){
//connected - do something
}
}

  当然,关于channel的读写操作,在讲Scatter/Gather时已经讲过,这里不再赘述。

  每次使用完成之后,都会调用 close() 方法关闭。需要说明的是,这里的 close() 方法支持异步关闭,当一个线程执行 close() 方法同时,另一个线程的在同一个通道上执行读操作时会被阻塞,然后这个读取操作不会返回任何数据,只会返回-1标识。当然,另一个线程如果是写操作的话也同样会被阻塞,不同的是会抛出 AsynchronousCloseException 异常。我们在上文中描述 InterruptibleChannel 这个可中断通道接口的时候也提到这个问题。

  channel.close();

  创建ServerSocketChannel与SocketChannel类似,不同的是ServerSocketChannel是监听套接字连接。所以在创建它的时候,需要将它绑定,如果没有绑定就执行 accept() 方法,那么会抛出 NotYetBoundException  异常。

  ServerSocketChannel channel = ServerSocketChannel.open();
channel.socket().bind(new InetSocketAddress(8080));

  监听到的连接可以使用 accept() 方法来返回该连接的 SocketChannel。执行 accept() 方法会一直阻塞直到有连接到达。

  SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();

Simple Server

  下面是一段简单的ServerSocketChannel和SocketChannel应用,可以看到是如何使用这两个类的。当浏览器输入localhost:8080的时候,会在控制台打印出这个连接的请求信息。

   public static void main(String[] args) throws IOException {
ServerSocketChannel channel = ServerSocketChannel.open();
channel.socket().bind(new InetSocketAddress(8080));
//设置非阻塞
channel.configureBlocking(false);
//注册
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_ACCEPT);
//处理器
Handler handler = new Handler(1024);
while (true) {
//等待请求,每次等待阻塞5s,5s后线程继续向下执行,如果传入0或者不传参数将一直阻塞
if (selector.select(5000) == 0) {
continue;
}
//获取待处理的SelectionKey
Iterator<SelectionKey> keyIterator = selector.selectedKeys().iterator();
while (keyIterator.hasNext()) {
SelectionKey key = keyIterator.next();
//当接受到请求
if (key.isAcceptable()) {
handler.handleAccept(key);
}
try {
//读数据
if (key.isReadable()) {
handler.handleRead(key);
}
} catch (IOException e) {
keyIterator.remove();
e.printStackTrace();
}
keyIterator.remove();
}
}
} private static class Handler {
private int bufferSize = 1024;
private String localCharset = "UTF-8"; public Handler() {
} public Handler(int bufferSize) {
this.bufferSize = bufferSize;
} public Handler(String localCharset) {
this.localCharset = localCharset;
} public Handler(int bufferSize, String localCharset) {
this.bufferSize = bufferSize;
this.localCharset = localCharset;
} public void handleAccept(SelectionKey key) {
try {
SocketChannel channel = ((ServerSocketChannel) key.channel()).accept();
channel.configureBlocking(false);
channel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
} catch (IOException e) {
e.printStackTrace();
}
} public void handleRead(SelectionKey key) throws IOException {
//获取channel
SocketChannel channel = (SocketChannel) key.channel();
//获取buffer 重置
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.clear();
if (channel.read(buffer) == -1) {
channel.close();
} else {
//转为读状态
buffer.flip();
String receivedString = Charset.forName(localCharset)
.newDecoder().decode(buffer).toString(); System.out.printf("接受到客户端数据" + receivedString); //返回数据给客户端
String sendString = "接受数据:" + receivedString;
buffer = ByteBuffer.wrap(sendString.getBytes(localCharset));
channel.write(buffer); channel.close();
}
}
}

NIO(三、Channel)的更多相关文章

  1. java学习-NIO(三)Channel

    通道(Channel)是java.nio的第二个主要创新.它们既不是一个扩展也不是一项增强,而是全新.极好的Java I/O示例,提供与I/O服务的直接连接.Channel用于在字节缓冲区和位于通道另 ...

  2. Java基础知识强化之IO流笔记73:NIO之 Channel

    1. Java NIO的Channel(通道)类似 Stream(流),但又有些不同: 既可以从通道中读取数据,又可以写数据到通道.但流的读写通常是单向的. 通道可以异步地读写. 通道中的数据总是要先 ...

  3. JAVA基础知识之NIO——Buffer.Channel,Charset,Channel文件锁

    NIO机制 NIO即NEW IO的意思,是JDK1.4提供的针对旧IO体系进行改进之后的IO,新增了许多新类,放在java.nio包下,并对java.io下许多类进行了修改,以便使用与nio. 在ja ...

  4. Java NIO教程 Channel

    Channel是一个连接到数据源的通道.程序不能直接用Channel中的数据,必须让Channel与BtyeBuffer交互数据,才能使用Buffer中的数据. 我们用FileChannel作为引子, ...

  5. (转)[疯狂Java]NIO:Channel的map映射

    原文出自:http://blog.csdn.net/lirx_tech/article/details/51396268 1. 通道映射技术: 1) 其实就是一种快速读写技术,它将通道所连接的数据节点 ...

  6. netty源码解解析(4.0)-12 Channel NIO实现:channel初始化

    创建一个channel实例,并把它register到eventLoopGroup中之后,这个channel然后处于inactive状态,仍然是不可用的.只有在bind或connect方法调用成功之后才 ...

  7. Java NIO 之 Channel(通道)

    历史回顾: Java NIO 概览 Java NIO 之 Buffer(缓冲区) 其他高赞文章: 面试中关于Redis的问题看这篇就够了 一文轻松搞懂redis集群原理及搭建与使用 一 Channel ...

  8. 【Java nio】Channel

    package com.slp.nio; import org.junit.Test; import java.io.*; import java.nio.ByteBuffer; import jav ...

  9. java nio之channel

    一.通道(Channel):由 java.nio.channels 包定义的.Channel 表示 IO 源与目标打开的连接.Channel 类似于传统的“流”.只不过 Channel本身不能直接访问 ...

  10. 【Java】NIO中Channel的注册源码分析

    Channel的注册是在SelectableChannel中定义的: public abstract SelectionKey register(Selector sel, int ops, Obje ...

随机推荐

  1. Codeforces374A

    A. Inna and Pink Pony time limit per test1 second memory limit per test 256 megabytes input standard ...

  2. 大大维的游戏机计划3--2048v1

    前几天由于忙着过年串门,游戏机的计划搁置了几天.这两天终于空出了一块时间,抽空写了2048. 由于笔者前面自制了一个类似2048的游戏,所以写起来也算是轻车熟路,花了两个晚上也就差不多了. 废话少说, ...

  3. cordova调用本地SQLite数据库的方法

    第一篇技术博客,写下来和大家分享今天所学,其次自己也巩固一下. 整个下午的时间用来钻研如何用cordova调用移动端本地SQLite数据库.首先我并不是用eclipse来编程的,而是用cordova建 ...

  4. synchronized的使用及注意事项

    主要来源:http://blog.csdn.net/luoweifu/article/details/46613015 1.synchronized(this) void method(){ sync ...

  5. UUID错误

    在Archive项目时,出现了“Your build settings specify a provisioning profile with the UUID “”, however, no suc ...

  6. Spring Boot 基础教程系列学习文档

    Spring Boot基础教程1-Spring Tool Suite工具的安装 Spring Boot基础教程2-RESTfull API简单项目的快速搭建 Spring Boot基础教程3-配置文件 ...

  7. CAN信号值解析

    本文提供一种可以解析CAN信号各信号值的一种方法并进行说明. 一般情况下,高端一点的设备会计算每一个信号的值,但是接受到CAN信号的报文实际上有各种情况,如何通过设定的起始位和数据长度来获取某一信号的 ...

  8. 从并发处理谈PHP进程间通信(二)System V IPC

    .container { margin-right: auto; margin-left: auto; padding-left: 15px; padding-right: 15px } .conta ...

  9. 浅谈sql优化

    问题的发现:      菜鸟D在工作的时候发现项目的sql语句很怪,例如 : select a.L_ZTBH, a.D_RQ, a.VC_BKDM, (select t.vc_name from tb ...

  10. 纪中集训 Day 4

    今天(其实是昨天)不考试= = 所以就刷题了 = = 早上无所事事,想把几道题刷过却很不爽的全删了 下午觉得不能这样了,把BZOJ 过了两道水的DP (计算几何根本不会啊QAQ) 晚上先水了一题之后, ...