公司做网站注意事项,医疗网站建设信息,网站用什么框架,南阳网站推广外包一、NIO简介
NIO 是 Java SE 1.4 引入的一组新的 I/O 相关的 API#xff0c;它提供了非阻塞式 I/O、选择器、通道、缓冲区等新的概念和机制。相比与传统的 I/O 多出的 N 不是单纯的 New#xff0c;更多的是代表了 Non-blocking 非阻塞#xff0c;NIO具有更高的并发性、可扩…一、NIO简介
NIO 是 Java SE 1.4 引入的一组新的 I/O 相关的 API它提供了非阻塞式 I/O、选择器、通道、缓冲区等新的概念和机制。相比与传统的 I/O 多出的 N 不是单纯的 New更多的是代表了 Non-blocking 非阻塞NIO具有更高的并发性、可扩展性以及更少的资源消耗等优点。
二、NIO 与传统BIO
NIO是同步非阻塞的服务器实现模式为 一个线程处理多个连接。服务端只会创建一个线程复杂管理Selector多路复用器Selector多路复用器不断的轮询注册其上的Channel通道中的 I/O 事件并将监听到的事件进行相应的处理。每个客户端与服务端建立连接时会创建一个 SocketChannel 通道通过 SocketChannel 进行数据交互。
BIO全称是Blocking IO同步阻塞式IO是JDK1.4之前的传统IO模型服务器实现模式为一个连接一个线程。每当客户端有连接请求时服务器端就需要启动一个线程进行处理。 两者主要区别如下
阻塞和非阻塞NIO 使用非阻塞式 I/O而 BIO 使用阻塞式 I/O。在阻塞式 I/O 中当一个 I/O 操作完成之前线程会一直被阻塞直到 I/O 操作完成在非阻塞式 I/O 中线程可以继续执行其他任务直到 I/O 操作完成并返回结果。线程模型NIO 中的线程模型是基于事件驱动的当一个 I/O 操作完成时会触发相应的事件通知线程处理而在 BIO 中每个线程都负责处理一个客户端连接需要不断地轮询客户端的输入输出流以便及时响应客户端的请求。内存消耗NIO 中使用的缓冲区Buffer可以重复利用减少了频繁的内存分配和回收从而减少了内存的消耗而在 BIO 中每个客户端连接都需要单独分配一个缓冲区容易造成内存的浪费。并发性能NIO 中使用非阻塞式 I/O可以同时处理多个客户端连接从而提高了并发处理能力而在 BIO 中由于每个客户端连接都需要一个线程来处理当连接数量增加时容易出现线程饥饿和资源耗尽的问题。
三、NIO 工作流程
创建 SelectorSelector 是 NIO 的核心组件之一它可以同时监听多个通道上的 I/O 事件并且可以通过 select() 方法等待事件的发生。注册 Channel通过 Selector 的 register() 方法将 Channel 注册到 Selector 上这样 Selector 就可以监听 Channel 上的 I/O 事件。等待事件调用 Selector 的 select() 方法等待事件的发生当有事件发生时Selector 就会通知相应的线程进行处理。处理事件当事件发生时Selector 会通知相应的线程进行处理可以通过 Channel 读取数据也可以通过 Channel 写入数据。关闭 Channel当 Channel 不再需要使用时需要调用 Channel 的 close() 方法关闭 Channel同时也需要调用 Buffer 的 clear() 方法清空 Buffer 中的数据以释放内存资源。
Java NIO 的工作流程可以简单概括为通过 Selector 监听多个 Channel 上的 I/O 事件当事件发生时通过对应的 Channel 进行读写操作并在 Channel 不再需要使用时关闭 Channel。
四、NIO 核心的组件
1. Channel(通道)
Channel 是应用程序与操作系统之间交互事件和传递内容的直接交互渠道应用程序可以从管道中读取操作系统中接收到的数据也可以向操作系统发送数据。Channel和传统IO中的Stream很相似其主要区别为通道是双向的通过一个Channel既可以进行读也可以进行写而Stream只能进行单向操作通过一个Stream只能进行读或者写比如InputStream只能进行读取操作OutputStream只能进行写操作。
1.1 常用的Channel实现类
FileChannel本地文件IO通道从文件中读写数据。一般流程为
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 自己的 bytebuf.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();SocketChannel网络套接字IO通道TCP协议客户端通过 SocketChannel 与服务端建立TCP连接进行通信交互。与传统的Socket操作不同的是SocketChannel基于非阻塞IO模式可以在同一个线程内同时管理多个通信连接从而提高系统的并发处理能力。
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();ServerSocketChannel网络套接字IO通道TCP协议服务端通过ServerSocketChannel监听来自客户端的连接请求并创建相应的SocketChannel对象进行通信交互。ServerSocketChannel同样也是基于非阻塞IO模式可以在同一个线程内同时管理多个通信连接从而提高系统的并发处理能力。
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();DatagramChannelDatagramChannel是Java NIO中对UDP协议通信的封装。通过DatagramChannel对象我们可以实现发送和接收UDP数据包。它与TCP协议不同的是UDP协议没有连接的概念所以无需像SocketChannel一样先建立连接再开始通信。
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();1.2 常用的Channel方法
read(ByteBuffer)从 Channel 中读取数据到 ByteBuffer 中。如果 Channel 中没有可读数据则会阻塞等待直到有数据可读。write(ByteBuffer)将数据写入到 Channel 中。如果 Channel 中没有可写空间则会阻塞等待直到有可写空间。read(ByteBuffer, long)从 Channel 中读取数据到 ByteBuffer 中并设置读取超时时间。如果超时时间到了还没有读取到数据则会抛出 TimeoutException 异常。write(ByteBuffer, long)将数据写入到 Channel 中并设置写入超时时间。如果超时时间到了还没有写入完成则会抛出 TimeoutException 异常。flush()将 Channel 中的缓冲区数据刷新到底层设备中如果没有数据需要刷新则会立即返回。register(SelectionKey, int)将 Channel 注册到 Selector 上并设置注册的事件类型和操作。可以通过 Selector 监听 Channel 上的事件当有事件发生时Selector 就会通知相应的线程进行处理。configureBlocking(boolean)设置 Channel 是否为阻塞模式。如果为阻塞模式则在读取或写入数据时会一直阻塞等待直到有数据可读或写入完成如果为非阻塞模式则在读取或写入数据时会立即返回如果没有数据可读或写入完成则会返回 -1。socket()获取底层的 Socket 对象。isConnected()判断 Channel 是否已经连接到了远程主机。isWritable()判断 Channel 是否可以写入数据。isReadable()判断 Channel 是否可以读取数据。isOpen()检查 Channel 是否已经打开。getRemoteAddress()获取 Channel 对应的远程地址。getLocalAddress()获取 Channel 对应的本地地址。
2. Buffer(缓冲区)
NIO 中的数据都是通过 Buffer 对象来处理的每个 Buffer 对象都关联着一个字节数组可以保存多个相同类型的数据。在读取数据时是从Buffer 中读取的在写入数据时也是写入到 Buffer 中的。
2.1 Buffer 常用子类
ByteBuffer用于存储字节数据CharBuffer用于存储字符数据ShortBuffer用于存储Short类型数据IntBuffer用于存储Int类型数据LongBuffer用于存储Long类型数据FloatBuffer用于存储Float类型数据DoubleBuffer用于存储Double类型数据
2.2 Buffer 重要属性
capacity(容量)表示 Buffer 所占的内存大小capacity不能为负并且创建后不能更改。limit(限制)表示 Buffer 中可以操作数据的大小limit不能为负并且不能大于其capacity。写模式下表示最多能往 Buffer 里写多少数据即 limit 等于 Buffer 的capacity。读模式下表示你最多能读到多少数据其实就是能读到之前写入的所有数据。position(位置)表示下一个要读取或写入的数据的索引。缓冲区的位置不能为 负并且不能大于其限制。初始的 position 值为 0最大可为 capacity – 1。当一个 byte、long 等数据写到 Buffer 后 position 会向前移动到下一个可插入数据的 Buffer 单元。mark(标记)表示记录当前 position 的位置。可以通过 reset() 恢复到 mark 的位置。
2.3 Buffer 常见方法
clear()清空缓冲区并返回对缓冲区的引用flip()将缓冲区的界限设置为当前位置并将当前位置重置为 0capacity()返回 Buffer 的 capacity 大小limit()返回 Buffer 的界限(limit) 的位置limit(int n)将设置缓冲区界限为 n并返回一个具有新 limit 的缓冲区对象position()返回缓冲区的当前位置 positionposition(int n)将设置缓冲区的当前位置为 n 并返回修改后的 Buffer 对象mark()对缓冲区设置标记reset()将位置 position 转到以前设置的mark 所在的位置rewind()将位置设为为 0 取消设置的 markhasRemaining()判断缓冲区中是否还有元素get()读取单个字节get(byte[] dst)读取多个字节get(int index)读取指定索引位置的字节put(byte b)将给定单个字节写入缓冲区的当前位置put(byte[] src)将数组中的字节从当前位置依次写入到缓冲区中put(int index, byte b)将指定字节写入缓冲区的索引位置
2.4 Buffer 内存分配
普通缓冲区通过allocate()方法进行分配可以在jvm堆上申请堆上内存。如果要作IO操作会先从本进程的堆上内存复制到直接内存再利用本地IO处理。
ByteBuffer readBuffer ByteBuffer.allocate(1024);直接缓冲区通过allocateDirect()方法进行分配直接从本地内存中申请。如果要作IO操作直接从本地内存中利用本地IO处理。使用直接内存会具有更高的效率但是它比申请普通的堆内存需要耗费更高的性能。直接内存中的数据是在JVM之外的因此它不会占用应用的内存当有很大的数据要缓存并且它的生命周期又很长那么就比较适合使用直接内存。一般来说如果不是能带来很明显的性能提升还是推荐使用堆内存。
ByteBuffer directByteBuffer ByteBuffer.allocateDirect(1024);缓冲区分片通过slice()方法可以根据现有的缓冲区对象来创建一个子缓冲区即在现有缓冲区上切出一片来作为一个新的缓冲区但现有的缓冲区与创建的子缓冲区在底层数组层面上是数据共享的。
ByteBuffer readBuffer ByteBuffer.allocate(1024);
ByteBuffer sliceBuffer readBuffer.slice();只读缓冲区通过asReadOnlyBuffer()方法可以将任何常规缓冲区转换为只读缓冲区这个方法返回 一个与原缓冲区完全相同的缓冲区并与原缓冲区共享数据只不过它是只读的。如果原缓冲区的内容发生了变化只读缓冲区的内容也随之发生变化。
ByteBuffer readBuffer ByteBuffer.allocate(1024);
ByteBuffer readonlyBuffer readBuffer.asReadOnlyBuffer();3. Selector(选择器)
Selector 提供了选择已经就绪的任务的能力。Selector会不断的轮询注册在上面的所有channel如果某个channel为读写等事件做好准备那么就处于就绪状态通过Selector可以不断轮询发现出就绪的channel进行后续的IO操作。只要通过一个单独的线程就可以管理多个channel从而管理多个网络连接。这就是Nio与传统I/O最大的区别不用为每个连接都去创建一个线程。
3.1 Selector使用流程
1.获取选择器
Selector selector Selector.open();2.通道注册到选择器进行监听
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);3.获取可操作的 Channel
selector.select();4.获取可操作的 Channel 中的就绪事件集合
SetSelectionKey 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
}3.2 SelectionKey事件类型
每个 Channel向Selector 注册时都会创建一个 SelectionKey 对象通过 SelectionKey 对象向Selector 注册且 SelectionKey 中维护了 Channel 的事件。常见的四种事件如下
OP_READ当操作系统读缓冲区有数据可读时就绪。OP_WRITE当操作系统写缓冲区有空闲空间时就绪。OP_CONNECT当 SocketChannel.connect()请求连接成功后就绪该操作只给客户端使用。OP_ACCEPT当接收到一个客户端连接请求时就绪该操作只给服务器使用。
五、简单实例
1. 服务端
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();// 服务器配置为非阻塞 即异步IOthis.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()){// 获取准备就绪的channelif (selector.select() 0) {continue;}// 获取到对应的 SelectionKey 对象SetSelectionKey keys selector.selectedKeys();IteratorSelectionKey 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);}// 移除当前的keykeyIterator.remove();}}}/*** 客服端连接事件处理** param key* throws IOException*/private void accept(SelectionKey key) throws IOException {SocketChannel socketChannel this.serverSocketChannel.accept();socketChannel.configureBlocking(false);// 注册客户端读取事件到selectorsocketChannel.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();}
}2. 客户端
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();// 配置为非阻塞 即异步IOsc.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()){// 获取准备就绪的channelif (selector.select() 0) {continue;}// 获取到对应的 SelectionKey 对象SetSelectionKey keys selector.selectedKeys();System.out.println(all keys is:keys.size());IteratorSelectionKey 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);}// 移除当前的keyiterator.remove();}}}public static void main(String[] args) throws Exception {new NioClientTest(localhost, 8000).send();}
}