NIO 是 Java SE 1.4 引入的一组新的 I/O 相关的 API,它提供了非阻塞式 I/O、选择器、通道、缓冲区等新的概念和机制。相比与传统的 I/O 多出的 N 不是单纯的 New,更多的是代表了 Non-blocking 非阻塞,NIO具有更高的并发性、可扩展性以及更少的资源消耗等优点。
NIO:是同步非阻塞的,服务器实现模式为 一个线程处理多个连接。服务端只会创建一个线程复杂管理Selector(多路复用器),Selector(多路复用器)不断的轮询注册其上的Channel(通道)中的 I/O 事件,并将监听到的事件进行相应的处理。每个客户端与服务端建立连接时会创建一个 SocketChannel 通道,通过 SocketChannel 进行数据交互。
BIO:全称是Blocking IO,同步阻塞式IO,是JDK1.4之前的传统IO模型,服务器实现模式为一个连接一个线程。每当客户端有连接请求时服务器端就需要启动一个线程进行处理。
两者主要区别如下:
Java NIO 的工作流程可以简单概括为:通过 Selector 监听多个 Channel 上的 I/O 事件,当事件发生时,通过对应的 Channel 进行读写操作,并在 Channel 不再需要使用时关闭 Channel。
Channel 是应用程序与操作系统之间交互事件和传递内容的直接交互渠道,应用程序可以从管道中读取操作系统中接收到的数据,也可以向操作系统发送数据。Channel和传统IO中的Stream很相似,其主要区别为:通道是双向的,通过一个Channel既可以进行读,也可以进行写;而Stream只能进行单向操作,通过一个Stream只能进行读或者写,比如InputStream只能进行读取操作,OutputStream只能进行写操作。
1.获取文件通道,通过 FileChannel 的静态方法 open() 来获取,获取时需要指定文件路径和文件打开方式
FileChannel channel = FileChannel.open(Paths.get(fileName), StandardOpenOption.READ);
2.创建字节缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
3.读/写操作
(1)、读操作
// 循环读取通道中的数据,并写入到 buf 中
while (channel.read(buf) != -1){
// 缓存区切换到读模式
buf.flip();
// 读取 buf 中的数据
while (buf.position() < buf.limit()){
// 将buf中的数据追加到文件中
text.append((char)buf.get());
}
// 清空已经读取完成的 buffer,以便后续使用
buf.clear();
}
(2)、写操作
// 循环读取文件中的数据,并写入到 buf 中
for (int i = 0; i < text.length(); i++) {
// 填充缓冲区,需要将 2 字节的 char 强转为 1 自己的 byte
buf.put((byte)text.charAt(i));
// 缓存区已满或者已经遍历到最后一个字符
if (buf.position() == buf.limit() || i == text.length() - 1) {
// 将缓冲区由写模式置为读模式
buf.flip();
// 将缓冲区的数据写到通道
channel.write(buf);
// 清空已经读取完成的 buffer,以便后续使用
buf.clear();
}
}
4.将数据刷出到物理磁盘
channel.force(false);
5.关闭通道
channel.close();
1.打开一个 SocketChannel 通道
SocketChannel channel = SocketChannel.open();
2.连接到服务端
channel.connect(new InetSocketAddress("localhost", 9001));
3.分配缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
4.配置是否为阻塞方式(默认为阻塞方式)
channel.configureBlocking(false); // 配置通道为非阻塞模式
5.将channel的连接、读、写等事件注册到selector中,每个chanel只能注册一个事件,最后注册的一个生效,
同时注册多个事件可以使用"|"操作符将常量连接起来
Selector selector = Selector.open();
channel.register(selector, SelectionKey.OP_CONNECT | SelectionKey.OP_WRITE | SelectionKey.OP_READ);
6.与服务端进行读写操作
channel.read(buf);
channel.write(buf);
7.关闭通道
channel.close();
1.打开一个 ServerSocketChannel 通道
ServerSocketChannel serverChannel = ServerSocketChannel.open();
2.绑定本地端口
serverChannel.bind(new InetSocketAddress(9001));
3.配置是否为阻塞方式(默认为阻塞方式)
serverChannel.configureBlocking(false); // 配置通道为非阻塞模式
4.分配缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
5.将serverChannel 的连接、读、写等事件注册到selector中,每个chanel只能注册一个事件,最后注册的一个生效,
同时注册多个事件可以使用"|"操作符将常量连接起来
Selector selector = Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT| SelectionKey.OP_WRITE | SelectionKey.OP_READ);
6.与客服端进行读写操作
serverChannel.read(buf);
serverChannel.write(buf);
7.关闭通道
serverChannel.close();
1.打开一个 DatagramChannel 通道
DatagramChannel channel = DatagramChannel.open();
2.分配缓冲区
ByteBuffer buf = ByteBuffer.allocate(1024);
3.配置是否为阻塞方式(默认为阻塞方式)
channel.configureBlocking(false); // 配置通道为非阻塞模式
4.与客服端进行读写操作
buffer.flip();
// 发送消息给服务端
channel.send(buffer, new InetSocketAddress("localhost", 9001));
buffer.clear();
// 接收服务端的响应信息
channel.receive(buffer);
buffer.flip();
// 打印出响应信息
while (buffer.hasRemaining()) {
System.out.print((char) buffer.get());
}
buffer.clear();
7.关闭通道
channel.close();
NIO 中的数据都是通过 Buffer 对象来处理的,每个 Buffer 对象都关联着一个字节数组,可以保存多个相同类型的数据。在读取数据时,是从Buffer 中读取的,在写入数据时,也是写入到 Buffer 中的。
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
ByteBuffer directByteBuffer = ByteBuffer.allocateDirect(1024);
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
ByteBuffer sliceBuffer = readBuffer.slice();
ByteBuffer readBuffer = ByteBuffer.allocate(1024);
ByteBuffer readonlyBuffer = readBuffer.asReadOnlyBuffer();
Selector 提供了选择已经就绪的任务的能力。Selector会不断的轮询注册在上面的所有channel,如果某个channel为读写等事件做好准备,那么就处于就绪状态,通过Selector可以不断轮询发现出就绪的channel,进行后续的IO操作。只要通过一个单独的线程就可以管理多个channel,从而管理多个网络连接。这就是Nio与传统I/O最大的区别,不用为每个连接都去创建一个线程。
1.获取选择器
Selector selector = Selector.open();
2.通道注册到选择器,进行监听
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
3.获取可操作的 Channel
selector.select();
4.获取可操作的 Channel 中的就绪事件集合
Set<SelectionKey> keys = selector.selectedKeys();
5.处理就绪事件
while (keys.iterator().hasNext()){
SelectionKey key = keys.iterator().next();
if (!key.isValid()){
continue;
}
if (key.isAcceptable()){
accept(key);
}
if(key.isReadable()){
read(key);
}
if (key.isWritable()){
write(key);
}
keyIterator.remove(); //移除当前的key
}
每个 Channel向Selector 注册时,都会创建一个 SelectionKey 对象,通过 SelectionKey 对象向Selector 注册,且 SelectionKey 中维护了 Channel 的事件。常见的四种事件如下:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NioServiceTest {
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private ByteBuffer readBuffer = ByteBuffer.allocate(1024);//调整缓冲区大小为1024字节
private ByteBuffer sendBuffer = ByteBuffer.allocate(1024);
String str;
public NioServiceTest(int port) throws IOException {
// 打开服务器套接字通道
this.serverSocketChannel = ServerSocketChannel.open();
// 服务器配置为非阻塞 即异步IO
this.serverSocketChannel.configureBlocking(false);
// 绑定本地端口
this.serverSocketChannel.bind(new InetSocketAddress(port));
// 创建选择器
this.selector = Selector.open();
// 注册接收连接事件
this.serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}
public void handle() throws IOException {
// 无限判断当前线程状态,如果没有中断,就一直执行while内容。
while(!Thread.currentThread().isInterrupted()){
// 获取准备就绪的channel
if (selector.select() == 0) {
continue;
}
// 获取到对应的 SelectionKey 对象
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> keyIterator = keys.iterator();
// 遍历所有的 SelectionKey 对象
while (keyIterator.hasNext()){
// 根据不同的SelectionKey事件类型进行相应的处理
SelectionKey key = keyIterator.next();
if (!key.isValid()){
continue;
}
if (key.isAcceptable()){
accept(key);
}
if(key.isReadable()){
read(key);
}
// 移除当前的key
keyIterator.remove();
}
}
}
/**
* 客服端连接事件处理
*
* @param key
* @throws IOException
*/
private void accept(SelectionKey key) throws IOException {
SocketChannel socketChannel = this.serverSocketChannel.accept();
socketChannel.configureBlocking(false);
// 注册客户端读取事件到selector
socketChannel.register(selector, SelectionKey.OP_READ);
System.out.println("client connected " + socketChannel.getRemoteAddress());
}
/**
* 读取事件处理
*
* @param key
* @throws IOException
*/
private void read(SelectionKey key) throws IOException{
SocketChannel socketChannel = (SocketChannel) key.channel();
//清除缓冲区,准备接受新数据
this.readBuffer.clear();
int numRead;
try{
// 从 channel 中读取数据
numRead = socketChannel.read(this.readBuffer);
}catch (IOException e){
System.out.println("read failed");
key.cancel();
socketChannel.close();
return;
}
str = new String(readBuffer.array(),0,numRead);
System.out.println("read String is: " + str);
}
public static void main(String[] args) throws Exception {
System.out.println("sever start...");
new NioServiceTest(8000).handle();
}
}
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
public class NioClientTest {
ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
private SocketChannel sc;
private Selector selector;
public NioClientTest(String hostname, int port) throws IOException {
// 打开socket通道
sc = SocketChannel.open();
// 配置为非阻塞 即异步IO
sc.configureBlocking(false);
// 连接服务器端
sc.connect(new InetSocketAddress(hostname,port));
// 创建选择器
selector = Selector.open();
// 注册请求连接事件
sc.register(selector, SelectionKey.OP_CONNECT);
}
public void send() throws IOException{
Scanner scanner = new Scanner(System.in);
// 无限判断当前线程状态,如果没有中断,就一直执行while内容。
while (!Thread.currentThread().isInterrupted()){
// 获取准备就绪的channel
if (selector.select() == 0) {
continue;
}
// 获取到对应的 SelectionKey 对象
Set<SelectionKey> keys = selector.selectedKeys();
System.out.println("all keys is:"+keys.size());
Iterator<SelectionKey> iterator = keys.iterator();
// 遍历所有的 SelectionKey 对象
while (iterator.hasNext()){
SelectionKey key = iterator.next();
//判断此通道上是否在进行连接操作
if (key.isConnectable()){
sc.finishConnect();
//注册写操作
sc.register(selector, SelectionKey.OP_WRITE);
System.out.println("server connected...");
break;
}else if (key.isWritable()){
System.out.println("please input message:");
String message = scanner.nextLine();
writeBuffer.clear();
writeBuffer.put(message.getBytes());
//将缓冲区各标志复位,因为向里面put了数据标志被改变要想从中读取数据发向服务器,就要复位
writeBuffer.flip();
sc.write(writeBuffer);
}
// 移除当前的key
iterator.remove();
}
}
}
public static void main(String[] args) throws Exception {
new NioClientTest("localhost", 8000).send();
}
}