目录
NIO
一、概述
- jdk1.4出现的同步式非阻塞流 ;
- 由三大组件组成:Buffer 缓冲区(存储数据)、Channel 通道(传输数据)、Selector 多路复用选择器;
二、Buffer 缓冲区
Buffer缓冲区用于数据的存储,低层是基于数组实现的,JDK提供了缓冲区的父类 – Buffer,是一个抽象类。Buffer只能存储除boolean以外的其他七种基本类型的数据,boolean以及其他类型的数据需要转化为其中的一种才能进行传输,最常用的是ByteBuffer。
Buffer缓冲区的四个重要位置
- capacity:容量位,用于标记缓冲区容量的大小;
- limit:限制位,用于限制position所能达到的最大的下标;
- position:操作位,指向要操作的下标;
- mark:标记位,可以标记正确写入到某个位置。
Buffer存在几个重要的位置变量capacity、limit、position和mark,make的初始值为-1,默认不开启make。
四个位置的大小排序应该是capacity>=limit>=position>=mark。
Buffer缓冲区的四个常用的操作
- flip:反转缓冲区,写数据结束后需要进行该操作
public final Buffer flip() {
limit = position;
position = 0;
mark = -1;
return this;
}
- clear:清空缓冲区,将缓冲区回归到最原始的状态(数据未被擦除)
public final Buffer clear() {
position = 0;
limit = capacity;
mark = -1;
return this;
}
- reset:重置缓冲区,将操作位移动到标记位
public final Buffer reset() {
int m = mark;
if (m < 0)
throw new InvalidMarkException();
position = m;
return this;
}
- rewind:重绕缓冲区,比翻转少了对限制位的操作,重新开始读写
public final Buffer rewind() {
position = 0;
mark = -1;
return this;
}
Buffer的两种常用创建方式
- 指定初始化大小
ByteBuffer byteBuffer = ByteBuffer.allocate(10);
- 指定初始化内容,初始化后操作位仍然为0
ByteBuffer byteBuffer = ByteBuffer.wrap(array[]);
三、Channel 管道
Channel用于数据的双向传输,本身面向缓冲区操作,所传输的数据必须放到缓冲区当中,通道默认是阻塞的,可以手动设置为非阻塞 。
Channel的初始化
Channel初始化的时候不能够使用new,否则会有很多需要实现的抽象方法,我们需要使用channel本身自带的方法去创建Channel。
// 创建客户端的通道
SocketChannel sc = SocketChannel.open();
Channel的阻塞设置
Channel默认是阻塞的,可以手动设置为非阻塞。
// 创建客户端的通道
SocketChannel sc = SocketChannel.open();
// Channel默认是阻塞的,可以手动设置为非阻塞
sc.configureBlocking(false);
四、Selector 选择器
多个客户端与多个服务器之间在早期存在惊群现象,及客户端的一个请求试图发送给所有服务器,所有服务器试图争抢一个请求,这会大大增加服务器的负担,Selector解决了这一问题。在使用Selector时,要求选择器所选择的通道必须是非阻塞的,Selector提供了三种针对服务端事件:
- ACCEPT:可接受事件
- READ:可读事件
- WRITE:可写事件
客户端样例
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class Client {
public static void main(String[] args) throws IOException {
SocketChannel sc = SocketChannel.open();
sc.connect(new InetSocketAddress("localhost", 8090));
sc.write(ByteBuffer.wrap("hello".getBytes()));
ByteBuffer buffer = ByteBuffer.allocate(10);
sc.read(buffer);
System.out.println(new String(buffer.array(), 0, buffer.position()));
while(true);
}
}
服务端样例
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.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class Server {
public static void main(String[] args) throws IOException {
// 开启服务器端的通道
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.bind(new InetSocketAddress(8090));
// 要求选择器所选择的通道必须是非阻塞的
ssc.configureBlocking(false);
// 获取到选择器
Selector selc = Selector.open();
// 把通道注册到选择器上让选择器去管理这个通道
// 表示当前服务器在选择器上注册并且注册了一个可接受的事件
ssc.register(selc, SelectionKey.OP_ACCEPT);
while (true) {
// 选择注册到选择器上的服务器
selc.select();
// 获取注册的事件
// 这个集合中存储的应该是accept/read/write事件
Set<SelectionKey> set = selc.selectedKeys();
// 遍历这些事件然后按照不同的事件来进行处理
Iterator<SelectionKey> it = set.iterator();
while (it.hasNext()) {
SelectionKey key = (SelectionKey) it.next();
// 接受连接
if (key.isAcceptable()) {
// 从这个事件中获取到对应的通道
ServerSocketChannel sscx = (ServerSocketChannel) key.channel();
SocketChannel sc = sscx.accept();
while (sc == null)
sc = sscx.accept();
System.out.println("连接成功~~~");
sc.configureBlocking(false);
// 表示同时注册了可读和可写事件
sc.register(selc, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
}
// 可读
if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
// 读取数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
sc.read(buffer);
System.out.println(new String(buffer.array(), 0, buffer.position()));
}
// 可写
if (key.isWritable()) {
SocketChannel sc = (SocketChannel) key.channel();
sc.write(ByteBuffer.wrap("hi".getBytes()));
}
}
// 事件在处理完成之后需要移除
it.remove();
}
}
}
五、NIO与BIO对比
一、BIO的缺点
1. 阻塞:导致效率降低
2. 一对一连接:导致在客户端大量连入的情况下,会服务器资源的紧张
3. 当客户端连入之后如果不做任何操作也会导致连接一直保持,导致服务器资源的浪费
4. 流式传输
二、NIO的优点
1. 可以手动设置为非阻塞
2. 一对多连接:可以利用一个或者少量的服务器线程来处理大量的请求
3. 每一个请求过来都需要对应的事件来能被处理
4. 采用缓冲区来存储数据,可以对缓冲区进行定点操作
三、NIO的缺点
NIO适用于短任务场景不适合于长任务场景,